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.File;
53 import java.io.IOException;
54 import java.util.ArrayList;
55 import java.util.HashMap;
56 import java.util.Iterator;
57 import java.util.Map;
58 import com.lowagie.text.error_messages.MessageLocalization;
59 
60 import com.lowagie.text.Document;
61 import com.lowagie.text.DocumentException;
62 import com.lowagie.text.ExceptionConverter;
63 
64 /** Reads a Truetype font
65  *
66  * @author Paulo Soares (psoares@consiste.pt)
67  */
68 class TrueTypeFont extends BaseFont {
69 
70     /** The code pages possible for a True Type font.
71      */
72     static final String codePages[] = {
73         "1252 Latin 1",
74         "1250 Latin 2: Eastern Europe",
75         "1251 Cyrillic",
76         "1253 Greek",
77         "1254 Turkish",
78         "1255 Hebrew",
79         "1256 Arabic",
80         "1257 Windows Baltic",
81         "1258 Vietnamese",
82         null,
83         null,
84         null,
85         null,
86         null,
87         null,
88         null,
89         "874 Thai",
90         "932 JIS/Japan",
91         "936 Chinese: Simplified chars--PRC and Singapore",
92         "949 Korean Wansung",
93         "950 Chinese: Traditional chars--Taiwan and Hong Kong",
94         "1361 Korean Johab",
95         null,
96         null,
97         null,
98         null,
99         null,
100         null,
101         null,
102         "Macintosh Character Set (US Roman)",
103         "OEM Character Set",
104         "Symbol Character Set",
105         null,
106         null,
107         null,
108         null,
109         null,
110         null,
111         null,
112         null,
113         null,
114         null,
115         null,
116         null,
117         null,
118         null,
119         null,
120         null,
121         "869 IBM Greek",
122         "866 MS-DOS Russian",
123         "865 MS-DOS Nordic",
124         "864 Arabic",
125         "863 MS-DOS Canadian French",
126         "862 Hebrew",
127         "861 MS-DOS Icelandic",
128         "860 MS-DOS Portuguese",
129         "857 IBM Turkish",
130         "855 IBM Cyrillic; primarily Russian",
131         "852 Latin 2",
132         "775 MS-DOS Baltic",
133         "737 Greek; former 437 G",
134         "708 Arabic; ASMO 708",
135         "850 WE/Latin 1",
136         "437 US"};
137 
138     protected boolean justNames = false;
139     /** Contains the location of the several tables. The key is the name of
140      * the table and the value is an <CODE>int[2]</CODE> where position 0
141      * is the offset from the start of the file and position 1 is the length
142      * of the table.
143      */
144     protected HashMap tables;
145     /** The file in use.
146      */
147     protected RandomAccessFileOrArray rf;
148     /** The file name.
149      */
150     protected String fileName;
151 
152     protected boolean cff = false;
153 
154     protected int cffOffset;
155 
156     protected int cffLength;
157 
158     /** The offset from the start of the file to the table directory.
159      * It is 0 for TTF and may vary for TTC depending on the chosen font.
160      */
161     protected int directoryOffset;
162     /** The index for the TTC font. It is an empty <CODE>String</CODE> for a
163      * TTF file.
164      */
165     protected String ttcIndex;
166     /** The style modifier */
167     protected String style = "";
168     /** The content of table 'head'.
169      */
170     protected FontHeader head = new FontHeader();
171     /** The content of table 'hhea'.
172      */
173     protected HorizontalHeader hhea = new HorizontalHeader();
174     /** The content of table 'OS/2'.
175      */
176     protected WindowsMetrics os_2 = new WindowsMetrics();
177     /** The width of the glyphs. This is essentially the content of table
178      * 'hmtx' normalized to 1000 units.
179      */
180     protected int GlyphWidths[];
181 
182     protected int bboxes[][];
183     /** The map containing the code information for the table 'cmap', encoding 1.0.
184      * The key is the code and the value is an <CODE>int[2]</CODE> where position 0
185      * is the glyph number and position 1 is the glyph width normalized to 1000
186      * units.
187      */
188     protected HashMap cmap10;
189     /** The map containing the code information for the table 'cmap', encoding 3.1
190      * in Unicode.
191      * <P>
192      * The key is the code and the value is an <CODE>int</CODE>[2] where position 0
193      * is the glyph number and position 1 is the glyph width normalized to 1000
194      * units.
195      */
196     protected HashMap cmap31;
197 
198     protected HashMap cmapExt;
199 
200     /** The map containing the kerning information. It represents the content of
201      * table 'kern'. The key is an <CODE>Integer</CODE> where the top 16 bits
202      * are the glyph number for the first character and the lower 16 bits are the
203      * glyph number for the second character. The value is the amount of kerning in
204      * normalized 1000 units as an <CODE>Integer</CODE>. This value is usually negative.
205      */
206     protected IntHashtable kerning = new IntHashtable();
207     /**
208      * The font name.
209      * This name is usually extracted from the table 'name' with
210      * the 'Name ID' 6.
211      */
212     protected String fontName;
213 
214     /** The full name of the font
215      */
216     protected String fullName[][];
217 
218     /** All the names of the Names-Table
219      */
220     protected String allNameEntries[][];
221 
222     /** The family name of the font
223      */
224     protected String familyName[][];
225     /** The italic angle. It is usually extracted from the 'post' table or in it's
226      * absence with the code:
227      * <P>
228      * <PRE>
229      * -Math.atan2(hhea.caretSlopeRun, hhea.caretSlopeRise) * 180 / Math.PI
230      * </PRE>
231      */
232     protected double italicAngle;
233     /** <CODE>true</CODE> if all the glyphs have the same width.
234      */
235     protected boolean isFixedPitch = false;
236 
237     protected int underlinePosition;
238 
239     protected int underlineThickness;
240 
241     /** The components of table 'head'.
242      */
243     protected static class FontHeader {
244         /** A variable. */
245         int flags;
246         /** A variable. */
247         int unitsPerEm;
248         /** A variable. */
249         short xMin;
250         /** A variable. */
251         short yMin;
252         /** A variable. */
253         short xMax;
254         /** A variable. */
255         short yMax;
256         /** A variable. */
257         int macStyle;
258     }
259 
260     /** The components of table 'hhea'.
261      */
262     protected static class HorizontalHeader {
263         /** A variable. */
264         short Ascender;
265         /** A variable. */
266         short Descender;
267         /** A variable. */
268         short LineGap;
269         /** A variable. */
270         int advanceWidthMax;
271         /** A variable. */
272         short minLeftSideBearing;
273         /** A variable. */
274         short minRightSideBearing;
275         /** A variable. */
276         short xMaxExtent;
277         /** A variable. */
278         short caretSlopeRise;
279         /** A variable. */
280         short caretSlopeRun;
281         /** A variable. */
282         int numberOfHMetrics;
283     }
284 
285     /** The components of table 'OS/2'.
286      */
287     protected static class WindowsMetrics {
288         /** A variable. */
289         short xAvgCharWidth;
290         /** A variable. */
291         int usWeightClass;
292         /** A variable. */
293         int usWidthClass;
294         /** A variable. */
295         short fsType;
296         /** A variable. */
297         short ySubscriptXSize;
298         /** A variable. */
299         short ySubscriptYSize;
300         /** A variable. */
301         short ySubscriptXOffset;
302         /** A variable. */
303         short ySubscriptYOffset;
304         /** A variable. */
305         short ySuperscriptXSize;
306         /** A variable. */
307         short ySuperscriptYSize;
308         /** A variable. */
309         short ySuperscriptXOffset;
310         /** A variable. */
311         short ySuperscriptYOffset;
312         /** A variable. */
313         short yStrikeoutSize;
314         /** A variable. */
315         short yStrikeoutPosition;
316         /** A variable. */
317         short sFamilyClass;
318         /** A variable. */
319         byte panose[] = new byte[10];
320         /** A variable. */
321         byte achVendID[] = new byte[4];
322         /** A variable. */
323         int fsSelection;
324         /** A variable. */
325         int usFirstCharIndex;
326         /** A variable. */
327         int usLastCharIndex;
328         /** A variable. */
329         short sTypoAscender;
330         /** A variable. */
331         short sTypoDescender;
332         /** A variable. */
333         short sTypoLineGap;
334         /** A variable. */
335         int usWinAscent;
336         /** A variable. */
337         int usWinDescent;
338         /** A variable. */
339         int ulCodePageRange1;
340         /** A variable. */
341         int ulCodePageRange2;
342         /** A variable. */
343         int sCapHeight;
344     }
345 
346     /** This constructor is present to allow extending the class.
347      */
TrueTypeFont()348     protected TrueTypeFont() {
349     }
350 
351     /** Creates a new TrueType font.
352      * @param ttFile the location of the font on file. The file must end in '.ttf' or
353      * '.ttc' but can have modifiers after the name
354      * @param enc the encoding to be applied to this font
355      * @param emb true if the font is to be embedded in the PDF
356      * @param ttfAfm the font as a <CODE>byte</CODE> array
357      * @throws DocumentException the font is invalid
358      * @throws IOException the font file could not be read
359      * @since	2.1.5
360      */
TrueTypeFont(String ttFile, String enc, boolean emb, byte ttfAfm[], boolean justNames, boolean forceRead)361     TrueTypeFont(String ttFile, String enc, boolean emb, byte ttfAfm[], boolean justNames, boolean forceRead) throws DocumentException, IOException {
362     	this.justNames = justNames;
363         String nameBase = getBaseName(ttFile);
364         String ttcName = getTTCName(nameBase);
365         if (nameBase.length() < ttFile.length()) {
366             style = ttFile.substring(nameBase.length());
367         }
368         encoding = enc;
369         embedded = emb;
370         fileName = ttcName;
371         fontType = FONT_TYPE_TT;
372         ttcIndex = "";
373         if (ttcName.length() < nameBase.length())
374             ttcIndex = nameBase.substring(ttcName.length() + 1);
375         if (fileName.toLowerCase().endsWith(".ttf") || fileName.toLowerCase().endsWith(".otf") || fileName.toLowerCase().endsWith(".ttc")) {
376             process(ttfAfm, forceRead);
377             if (!justNames && embedded && os_2.fsType == 2)
378                 throw new DocumentException(MessageLocalization.getComposedMessage("1.cannot.be.embedded.due.to.licensing.restrictions", fileName + style));
379         }
380         else
381             throw new DocumentException(MessageLocalization.getComposedMessage("1.is.not.a.ttf.otf.or.ttc.font.file", fileName + style));
382         if (!encoding.startsWith("#"))
383             PdfEncodings.convertToBytes(" ", enc); // check if the encoding exists
384         createEncoding();
385     }
386 
387     /** Gets the name from a composed TTC file name.
388      * If I have for input "myfont.ttc,2" the return will
389      * be "myfont.ttc".
390      * @param name the full name
391      * @return the simple file name
392      */
getTTCName(String name)393     protected static String getTTCName(String name) {
394         int idx = name.toLowerCase().indexOf(".ttc,");
395         if (idx < 0)
396             return name;
397         else
398             return name.substring(0, idx + 4);
399     }
400 
401 
402     /**
403      * Reads the tables 'head', 'hhea', 'OS/2' and 'post' filling several variables.
404      * @throws DocumentException the font is invalid
405      * @throws IOException the font file could not be read
406      */
fillTables()407     void fillTables() throws DocumentException, IOException {
408         int table_location[];
409         table_location = (int[])tables.get("head");
410         if (table_location == null)
411             throw new DocumentException(MessageLocalization.getComposedMessage("table.1.does.not.exist.in.2", "head", fileName + style));
412         rf.seek(table_location[0] + 16);
413         head.flags = rf.readUnsignedShort();
414         head.unitsPerEm = rf.readUnsignedShort();
415         rf.skipBytes(16);
416         head.xMin = rf.readShort();
417         head.yMin = rf.readShort();
418         head.xMax = rf.readShort();
419         head.yMax = rf.readShort();
420         head.macStyle = rf.readUnsignedShort();
421 
422         table_location = (int[])tables.get("hhea");
423         if (table_location == null)
424             throw new DocumentException(MessageLocalization.getComposedMessage("table.1.does.not.exist.in.2", "hhea", fileName + style));
425         rf.seek(table_location[0] + 4);
426         hhea.Ascender = rf.readShort();
427         hhea.Descender = rf.readShort();
428         hhea.LineGap = rf.readShort();
429         hhea.advanceWidthMax = rf.readUnsignedShort();
430         hhea.minLeftSideBearing = rf.readShort();
431         hhea.minRightSideBearing = rf.readShort();
432         hhea.xMaxExtent = rf.readShort();
433         hhea.caretSlopeRise = rf.readShort();
434         hhea.caretSlopeRun = rf.readShort();
435         rf.skipBytes(12);
436         hhea.numberOfHMetrics = rf.readUnsignedShort();
437 
438         table_location = (int[])tables.get("OS/2");
439         if (table_location == null)
440             throw new DocumentException(MessageLocalization.getComposedMessage("table.1.does.not.exist.in.2", "OS/2", fileName + style));
441         rf.seek(table_location[0]);
442         int version = rf.readUnsignedShort();
443         os_2.xAvgCharWidth = rf.readShort();
444         os_2.usWeightClass = rf.readUnsignedShort();
445         os_2.usWidthClass = rf.readUnsignedShort();
446         os_2.fsType = rf.readShort();
447         os_2.ySubscriptXSize = rf.readShort();
448         os_2.ySubscriptYSize = rf.readShort();
449         os_2.ySubscriptXOffset = rf.readShort();
450         os_2.ySubscriptYOffset = rf.readShort();
451         os_2.ySuperscriptXSize = rf.readShort();
452         os_2.ySuperscriptYSize = rf.readShort();
453         os_2.ySuperscriptXOffset = rf.readShort();
454         os_2.ySuperscriptYOffset = rf.readShort();
455         os_2.yStrikeoutSize = rf.readShort();
456         os_2.yStrikeoutPosition = rf.readShort();
457         os_2.sFamilyClass = rf.readShort();
458         rf.readFully(os_2.panose);
459         rf.skipBytes(16);
460         rf.readFully(os_2.achVendID);
461         os_2.fsSelection = rf.readUnsignedShort();
462         os_2.usFirstCharIndex = rf.readUnsignedShort();
463         os_2.usLastCharIndex = rf.readUnsignedShort();
464         os_2.sTypoAscender = rf.readShort();
465         os_2.sTypoDescender = rf.readShort();
466         if (os_2.sTypoDescender > 0)
467             os_2.sTypoDescender = (short)(-os_2.sTypoDescender);
468         os_2.sTypoLineGap = rf.readShort();
469         os_2.usWinAscent = rf.readUnsignedShort();
470         os_2.usWinDescent = rf.readUnsignedShort();
471         os_2.ulCodePageRange1 = 0;
472         os_2.ulCodePageRange2 = 0;
473         if (version > 0) {
474             os_2.ulCodePageRange1 = rf.readInt();
475             os_2.ulCodePageRange2 = rf.readInt();
476         }
477         if (version > 1) {
478             rf.skipBytes(2);
479             os_2.sCapHeight = rf.readShort();
480         }
481         else
482             os_2.sCapHeight = (int)(0.7 * head.unitsPerEm);
483 
484         table_location = (int[])tables.get("post");
485         if (table_location == null) {
486             italicAngle = -Math.atan2(hhea.caretSlopeRun, hhea.caretSlopeRise) * 180 / Math.PI;
487             return;
488         }
489         rf.seek(table_location[0] + 4);
490         short mantissa = rf.readShort();
491         int fraction = rf.readUnsignedShort();
492         italicAngle = mantissa + fraction / 16384.0d;
493         underlinePosition = rf.readShort();
494         underlineThickness = rf.readShort();
495         isFixedPitch = rf.readInt() != 0;
496     }
497 
498     /**
499      * Gets the Postscript font name.
500      * @throws DocumentException the font is invalid
501      * @throws IOException the font file could not be read
502      * @return the Postscript font name
503      */
getBaseFont()504     String getBaseFont() throws DocumentException, IOException {
505         int table_location[];
506         table_location = (int[])tables.get("name");
507         if (table_location == null)
508             throw new DocumentException(MessageLocalization.getComposedMessage("table.1.does.not.exist.in.2", "name", fileName + style));
509         rf.seek(table_location[0] + 2);
510         int numRecords = rf.readUnsignedShort();
511         int startOfStorage = rf.readUnsignedShort();
512         for (int k = 0; k < numRecords; ++k) {
513             int platformID = rf.readUnsignedShort();
514             int platformEncodingID = rf.readUnsignedShort();
515             int languageID = rf.readUnsignedShort();
516             int nameID = rf.readUnsignedShort();
517             int length = rf.readUnsignedShort();
518             int offset = rf.readUnsignedShort();
519             if (nameID == 6) {
520                 rf.seek(table_location[0] + startOfStorage + offset);
521                 if (platformID == 0 || platformID == 3)
522                     return readUnicodeString(length);
523                 else
524                     return readStandardString(length);
525             }
526         }
527         File file = new File(fileName);
528         return file.getName().replace(' ', '-');
529     }
530 
531     /** Extracts the names of the font in all the languages available.
532      * @param id the name id to retrieve
533      * @throws DocumentException on error
534      * @throws IOException on error
535      */
getNames(int id)536     String[][] getNames(int id) throws DocumentException, IOException {
537         int table_location[];
538         table_location = (int[])tables.get("name");
539         if (table_location == null)
540             throw new DocumentException(MessageLocalization.getComposedMessage("table.1.does.not.exist.in.2", "name", fileName + style));
541         rf.seek(table_location[0] + 2);
542         int numRecords = rf.readUnsignedShort();
543         int startOfStorage = rf.readUnsignedShort();
544         ArrayList names = new ArrayList();
545         for (int k = 0; k < numRecords; ++k) {
546             int platformID = rf.readUnsignedShort();
547             int platformEncodingID = rf.readUnsignedShort();
548             int languageID = rf.readUnsignedShort();
549             int nameID = rf.readUnsignedShort();
550             int length = rf.readUnsignedShort();
551             int offset = rf.readUnsignedShort();
552             if (nameID == id) {
553                 int pos = rf.getFilePointer();
554                 rf.seek(table_location[0] + startOfStorage + offset);
555                 String name;
556                 if (platformID == 0 || platformID == 3 || (platformID == 2 && platformEncodingID == 1)){
557                     name = readUnicodeString(length);
558                 }
559                 else {
560                     name = readStandardString(length);
561                 }
562                 names.add(new String[]{String.valueOf(platformID),
563                     String.valueOf(platformEncodingID), String.valueOf(languageID), name});
564                 rf.seek(pos);
565             }
566         }
567         String thisName[][] = new String[names.size()][];
568         for (int k = 0; k < names.size(); ++k)
569             thisName[k] = (String[])names.get(k);
570         return thisName;
571     }
572 
573     /** Extracts all the names of the names-Table
574      * @throws DocumentException on error
575      * @throws IOException on error
576      */
getAllNames()577     String[][] getAllNames() throws DocumentException, IOException {
578         int table_location[];
579         table_location = (int[])tables.get("name");
580         if (table_location == null)
581             throw new DocumentException(MessageLocalization.getComposedMessage("table.1.does.not.exist.in.2", "name", fileName + style));
582         rf.seek(table_location[0] + 2);
583         int numRecords = rf.readUnsignedShort();
584         int startOfStorage = rf.readUnsignedShort();
585         ArrayList names = new ArrayList();
586         for (int k = 0; k < numRecords; ++k) {
587             int platformID = rf.readUnsignedShort();
588             int platformEncodingID = rf.readUnsignedShort();
589             int languageID = rf.readUnsignedShort();
590             int nameID = rf.readUnsignedShort();
591             int length = rf.readUnsignedShort();
592             int offset = rf.readUnsignedShort();
593             int pos = rf.getFilePointer();
594             rf.seek(table_location[0] + startOfStorage + offset);
595             String name;
596             if (platformID == 0 || platformID == 3 || (platformID == 2 && platformEncodingID == 1)){
597                 name = readUnicodeString(length);
598             }
599             else {
600                 name = readStandardString(length);
601             }
602             names.add(new String[]{String.valueOf(nameID), String.valueOf(platformID),
603                     String.valueOf(platformEncodingID), String.valueOf(languageID), name});
604             rf.seek(pos);
605         }
606         String thisName[][] = new String[names.size()][];
607         for (int k = 0; k < names.size(); ++k)
608             thisName[k] = (String[])names.get(k);
609         return thisName;
610     }
611 
checkCff()612     void checkCff() {
613         int table_location[];
614         table_location = (int[])tables.get("CFF ");
615         if (table_location != null) {
616             cff = true;
617             cffOffset = table_location[0];
618             cffLength = table_location[1];
619         }
620     }
621 
622     /** Reads the font data.
623      * @param ttfAfm the font as a <CODE>byte</CODE> array, possibly <CODE>null</CODE>
624      * @throws DocumentException the font is invalid
625      * @throws IOException the font file could not be read
626      * @since	2.1.5
627      */
process(byte ttfAfm[], boolean preload)628     void process(byte ttfAfm[], boolean preload) throws DocumentException, IOException {
629         tables = new HashMap();
630 
631         try {
632             if (ttfAfm == null)
633                 rf = new RandomAccessFileOrArray(fileName, preload, Document.plainRandomAccess);
634             else
635                 rf = new RandomAccessFileOrArray(ttfAfm);
636             if (ttcIndex.length() > 0) {
637                 int dirIdx = Integer.parseInt(ttcIndex);
638                 if (dirIdx < 0)
639                     throw new DocumentException(MessageLocalization.getComposedMessage("the.font.index.for.1.must.be.positive", fileName));
640                 String mainTag = readStandardString(4);
641                 if (!mainTag.equals("ttcf"))
642                     throw new DocumentException(MessageLocalization.getComposedMessage("1.is.not.a.valid.ttc.file", fileName));
643                 rf.skipBytes(4);
644                 int dirCount = rf.readInt();
645                 if (dirIdx >= dirCount)
646                     throw new DocumentException(MessageLocalization.getComposedMessage("the.font.index.for.1.must.be.between.0.and.2.it.was.3", fileName, String.valueOf(dirCount - 1), String.valueOf(dirIdx)));
647                 rf.skipBytes(dirIdx * 4);
648                 directoryOffset = rf.readInt();
649             }
650             rf.seek(directoryOffset);
651             int ttId = rf.readInt();
652             if (ttId != 0x00010000 && ttId != 0x4F54544F)
653                 throw new DocumentException(MessageLocalization.getComposedMessage("1.is.not.a.valid.ttf.or.otf.file", fileName));
654             int num_tables = rf.readUnsignedShort();
655             rf.skipBytes(6);
656             for (int k = 0; k < num_tables; ++k) {
657                 String tag = readStandardString(4);
658                 rf.skipBytes(4);
659                 int table_location[] = new int[2];
660                 table_location[0] = rf.readInt();
661                 table_location[1] = rf.readInt();
662                 tables.put(tag, table_location);
663             }
664             checkCff();
665             fontName = getBaseFont();
666             fullName = getNames(4); //full name
667             familyName = getNames(1); //family name
668             allNameEntries = getAllNames();
669             if (!justNames) {
670                 fillTables();
671                 readGlyphWidths();
672                 readCMaps();
673                 readKerning();
674                 readBbox();
675                 GlyphWidths = null;
676             }
677         }
678         finally {
679             if (rf != null) {
680                 rf.close();
681                 if (!embedded)
682                     rf = null;
683             }
684         }
685     }
686 
687     /** Reads a <CODE>String</CODE> from the font file as bytes using the Cp1252
688      *  encoding.
689      * @param length the length of bytes to read
690      * @return the <CODE>String</CODE> read
691      * @throws IOException the font file could not be read
692      */
readStandardString(int length)693     protected String readStandardString(int length) throws IOException {
694         byte buf[] = new byte[length];
695         rf.readFully(buf);
696         try {
697             return new String(buf, WINANSI);
698         }
699         catch (Exception e) {
700             throw new ExceptionConverter(e);
701         }
702     }
703 
704     /** Reads a Unicode <CODE>String</CODE> from the font file. Each character is
705      *  represented by two bytes.
706      * @param length the length of bytes to read. The <CODE>String</CODE> will have <CODE>length</CODE>/2
707      * characters
708      * @return the <CODE>String</CODE> read
709      * @throws IOException the font file could not be read
710      */
readUnicodeString(int length)711     protected String readUnicodeString(int length) throws IOException {
712         StringBuffer buf = new StringBuffer();
713         length /= 2;
714         for (int k = 0; k < length; ++k) {
715             buf.append(rf.readChar());
716         }
717         return buf.toString();
718     }
719 
720     /** Reads the glyphs widths. The widths are extracted from the table 'hmtx'.
721      *  The glyphs are normalized to 1000 units.
722      * @throws DocumentException the font is invalid
723      * @throws IOException the font file could not be read
724      */
readGlyphWidths()725     protected void readGlyphWidths() throws DocumentException, IOException {
726         int table_location[];
727         table_location = (int[])tables.get("hmtx");
728         if (table_location == null)
729             throw new DocumentException(MessageLocalization.getComposedMessage("table.1.does.not.exist.in.2", "hmtx", fileName + style));
730         rf.seek(table_location[0]);
731         GlyphWidths = new int[hhea.numberOfHMetrics];
732         for (int k = 0; k < hhea.numberOfHMetrics; ++k) {
733             GlyphWidths[k] = (rf.readUnsignedShort() * 1000) / head.unitsPerEm;
734             rf.readUnsignedShort();
735         }
736     }
737 
738     /** Gets a glyph width.
739      * @param glyph the glyph to get the width of
740      * @return the width of the glyph in normalized 1000 units
741      */
getGlyphWidth(int glyph)742     protected int getGlyphWidth(int glyph) {
743         if (glyph >= GlyphWidths.length)
744             glyph = GlyphWidths.length - 1;
745         return GlyphWidths[glyph];
746     }
747 
readBbox()748     private void readBbox() throws DocumentException, IOException {
749         int tableLocation[];
750         tableLocation = (int[])tables.get("head");
751         if (tableLocation == null)
752             throw new DocumentException(MessageLocalization.getComposedMessage("table.1.does.not.exist.in.2", "head", fileName + style));
753         rf.seek(tableLocation[0] + TrueTypeFontSubSet.HEAD_LOCA_FORMAT_OFFSET);
754         boolean locaShortTable = (rf.readUnsignedShort() == 0);
755         tableLocation = (int[])tables.get("loca");
756         if (tableLocation == null)
757             return;
758         rf.seek(tableLocation[0]);
759         int locaTable[];
760         if (locaShortTable) {
761             int entries = tableLocation[1] / 2;
762             locaTable = new int[entries];
763             for (int k = 0; k < entries; ++k)
764                 locaTable[k] = rf.readUnsignedShort() * 2;
765         }
766         else {
767             int entries = tableLocation[1] / 4;
768             locaTable = new int[entries];
769             for (int k = 0; k < entries; ++k)
770                 locaTable[k] = rf.readInt();
771         }
772         tableLocation = (int[])tables.get("glyf");
773         if (tableLocation == null)
774             throw new DocumentException(MessageLocalization.getComposedMessage("table.1.does.not.exist.in.2", "glyf", fileName + style));
775         int tableGlyphOffset = tableLocation[0];
776         bboxes = new int[locaTable.length - 1][];
777         for (int glyph = 0; glyph < locaTable.length - 1; ++glyph) {
778             int start = locaTable[glyph];
779             if (start != locaTable[glyph + 1]) {
780                 rf.seek(tableGlyphOffset + start + 2);
781                 bboxes[glyph] = new int[]{
782                     (rf.readShort() * 1000) / head.unitsPerEm,
783                     (rf.readShort() * 1000) / head.unitsPerEm,
784                     (rf.readShort() * 1000) / head.unitsPerEm,
785                     (rf.readShort() * 1000) / head.unitsPerEm};
786             }
787         }
788     }
789 
790     /** Reads the several maps from the table 'cmap'. The maps of interest are 1.0 for symbolic
791      *  fonts and 3.1 for all others. A symbolic font is defined as having the map 3.0.
792      * @throws DocumentException the font is invalid
793      * @throws IOException the font file could not be read
794      */
readCMaps()795     void readCMaps() throws DocumentException, IOException {
796         int table_location[];
797         table_location = (int[])tables.get("cmap");
798         if (table_location == null)
799             throw new DocumentException(MessageLocalization.getComposedMessage("table.1.does.not.exist.in.2", "cmap", fileName + style));
800         rf.seek(table_location[0]);
801         rf.skipBytes(2);
802         int num_tables = rf.readUnsignedShort();
803         fontSpecific = false;
804         int map10 = 0;
805         int map31 = 0;
806         int map30 = 0;
807         int mapExt = 0;
808         for (int k = 0; k < num_tables; ++k) {
809             int platId = rf.readUnsignedShort();
810             int platSpecId = rf.readUnsignedShort();
811             int offset = rf.readInt();
812             if (platId == 3 && platSpecId == 0) {
813                 fontSpecific = true;
814                 map30 = offset;
815             }
816             else if (platId == 3 && platSpecId == 1) {
817                 map31 = offset;
818             }
819             else if (platId == 3 && platSpecId == 10) {
820                 mapExt = offset;
821             }
822             if (platId == 1 && platSpecId == 0) {
823                 map10 = offset;
824             }
825         }
826         if (map10 > 0) {
827             rf.seek(table_location[0] + map10);
828             int format = rf.readUnsignedShort();
829             switch (format) {
830                 case 0:
831                     cmap10 = readFormat0();
832                     break;
833                 case 4:
834                     cmap10 = readFormat4();
835                     break;
836                 case 6:
837                     cmap10 = readFormat6();
838                     break;
839             }
840         }
841         if (map31 > 0) {
842             rf.seek(table_location[0] + map31);
843             int format = rf.readUnsignedShort();
844             if (format == 4) {
845                 cmap31 = readFormat4();
846             }
847         }
848         if (map30 > 0) {
849             rf.seek(table_location[0] + map30);
850             int format = rf.readUnsignedShort();
851             if (format == 4) {
852                 cmap10 = readFormat4();
853             }
854         }
855         if (mapExt > 0) {
856             rf.seek(table_location[0] + mapExt);
857             int format = rf.readUnsignedShort();
858             switch (format) {
859                 case 0:
860                     cmapExt = readFormat0();
861                     break;
862                 case 4:
863                     cmapExt = readFormat4();
864                     break;
865                 case 6:
866                     cmapExt = readFormat6();
867                     break;
868                 case 12:
869                     cmapExt = readFormat12();
870                     break;
871             }
872         }
873     }
874 
readFormat12()875     HashMap readFormat12() throws IOException {
876         HashMap h = new HashMap();
877         rf.skipBytes(2);
878         int table_lenght = rf.readInt();
879         rf.skipBytes(4);
880         int nGroups = rf.readInt();
881         for (int k = 0; k < nGroups; k++) {
882             int startCharCode = rf.readInt();
883             int endCharCode = rf.readInt();
884             int startGlyphID = rf.readInt();
885             for (int i = startCharCode; i <= endCharCode; i++) {
886                 int[] r = new int[2];
887                 r[0] = startGlyphID;
888                 r[1] = getGlyphWidth(r[0]);
889                 h.put(new Integer(i), r);
890                 startGlyphID++;
891             }
892         }
893         return h;
894     }
895 
896     /** The information in the maps of the table 'cmap' is coded in several formats.
897      *  Format 0 is the Apple standard character to glyph index mapping table.
898      * @return a <CODE>HashMap</CODE> representing this map
899      * @throws IOException the font file could not be read
900      */
readFormat0()901     HashMap readFormat0() throws IOException {
902         HashMap h = new HashMap();
903         rf.skipBytes(4);
904         for (int k = 0; k < 256; ++k) {
905             int r[] = new int[2];
906             r[0] = rf.readUnsignedByte();
907             r[1] = getGlyphWidth(r[0]);
908             h.put(new Integer(k), r);
909         }
910         return h;
911     }
912 
913     /** The information in the maps of the table 'cmap' is coded in several formats.
914      *  Format 4 is the Microsoft standard character to glyph index mapping table.
915      * @return a <CODE>HashMap</CODE> representing this map
916      * @throws IOException the font file could not be read
917      */
readFormat4()918     HashMap readFormat4() throws IOException {
919         HashMap h = new HashMap();
920         int table_lenght = rf.readUnsignedShort();
921         rf.skipBytes(2);
922         int segCount = rf.readUnsignedShort() / 2;
923         rf.skipBytes(6);
924         int endCount[] = new int[segCount];
925         for (int k = 0; k < segCount; ++k) {
926             endCount[k] = rf.readUnsignedShort();
927         }
928         rf.skipBytes(2);
929         int startCount[] = new int[segCount];
930         for (int k = 0; k < segCount; ++k) {
931             startCount[k] = rf.readUnsignedShort();
932         }
933         int idDelta[] = new int[segCount];
934         for (int k = 0; k < segCount; ++k) {
935             idDelta[k] = rf.readUnsignedShort();
936         }
937         int idRO[] = new int[segCount];
938         for (int k = 0; k < segCount; ++k) {
939             idRO[k] = rf.readUnsignedShort();
940         }
941         int glyphId[] = new int[table_lenght / 2 - 8 - segCount * 4];
942         for (int k = 0; k < glyphId.length; ++k) {
943             glyphId[k] = rf.readUnsignedShort();
944         }
945         for (int k = 0; k < segCount; ++k) {
946             int glyph;
947             for (int j = startCount[k]; j <= endCount[k] && j != 0xFFFF; ++j) {
948                 if (idRO[k] == 0) {
949                     glyph = (j + idDelta[k]) & 0xFFFF;
950                 }
951                 else {
952                     int idx = k + idRO[k] / 2 - segCount + j - startCount[k];
953                     if (idx >= glyphId.length)
954                         continue;
955                     glyph = (glyphId[idx] + idDelta[k]) & 0xFFFF;
956                 }
957                 int r[] = new int[2];
958                 r[0] = glyph;
959                 r[1] = getGlyphWidth(r[0]);
960                 h.put(new Integer(fontSpecific ? ((j & 0xff00) == 0xf000 ? j & 0xff : j) : j), r);
961             }
962         }
963         return h;
964     }
965 
966     /** The information in the maps of the table 'cmap' is coded in several formats.
967      *  Format 6 is a trimmed table mapping. It is similar to format 0 but can have
968      *  less than 256 entries.
969      * @return a <CODE>HashMap</CODE> representing this map
970      * @throws IOException the font file could not be read
971      */
readFormat6()972     HashMap readFormat6() throws IOException {
973         HashMap h = new HashMap();
974         rf.skipBytes(4);
975         int start_code = rf.readUnsignedShort();
976         int code_count = rf.readUnsignedShort();
977         for (int k = 0; k < code_count; ++k) {
978             int r[] = new int[2];
979             r[0] = rf.readUnsignedShort();
980             r[1] = getGlyphWidth(r[0]);
981             h.put(new Integer(k + start_code), r);
982         }
983         return h;
984     }
985 
986     /** Reads the kerning information from the 'kern' table.
987      * @throws IOException the font file could not be read
988      */
readKerning()989     void readKerning() throws IOException {
990         int table_location[];
991         table_location = (int[])tables.get("kern");
992         if (table_location == null)
993             return;
994         rf.seek(table_location[0] + 2);
995         int nTables = rf.readUnsignedShort();
996         int checkpoint = table_location[0] + 4;
997         int length = 0;
998         for (int k = 0; k < nTables; ++k) {
999             checkpoint += length;
1000             rf.seek(checkpoint);
1001             rf.skipBytes(2);
1002             length = rf.readUnsignedShort();
1003             int coverage = rf.readUnsignedShort();
1004             if ((coverage & 0xfff7) == 0x0001) {
1005                 int nPairs = rf.readUnsignedShort();
1006                 rf.skipBytes(6);
1007                 for (int j = 0; j < nPairs; ++j) {
1008                     int pair = rf.readInt();
1009                     int value = rf.readShort() * 1000 / head.unitsPerEm;
1010                     kerning.put(pair, value);
1011                 }
1012             }
1013         }
1014     }
1015 
1016     /** Gets the kerning between two Unicode chars.
1017      * @param char1 the first char
1018      * @param char2 the second char
1019      * @return the kerning to be applied
1020      */
getKerning(int char1, int char2)1021     public int getKerning(int char1, int char2) {
1022         int metrics[] = getMetricsTT(char1);
1023         if (metrics == null)
1024             return 0;
1025         int c1 = metrics[0];
1026         metrics = getMetricsTT(char2);
1027         if (metrics == null)
1028             return 0;
1029         int c2 = metrics[0];
1030         return kerning.get((c1 << 16) + c2);
1031     }
1032 
1033     /** Gets the width from the font according to the unicode char <CODE>c</CODE>.
1034      * If the <CODE>name</CODE> is null it's a symbolic font.
1035      * @param c the unicode char
1036      * @param name the glyph name
1037      * @return the width of the char
1038      */
getRawWidth(int c, String name)1039     int getRawWidth(int c, String name) {
1040         int[] metric = getMetricsTT(c);
1041         if (metric == null)
1042             return 0;
1043         return metric[1];
1044     }
1045 
1046     /** Generates the font descriptor for this font.
1047      * @return the PdfDictionary containing the font descriptor or <CODE>null</CODE>
1048      * @param subsetPrefix the subset prefix
1049      * @param fontStream the indirect reference to a PdfStream containing the font or <CODE>null</CODE>
1050      */
getFontDescriptor(PdfIndirectReference fontStream, String subsetPrefix, PdfIndirectReference cidset)1051     protected PdfDictionary getFontDescriptor(PdfIndirectReference fontStream, String subsetPrefix, PdfIndirectReference cidset) {
1052         PdfDictionary dic = new PdfDictionary(PdfName.FONTDESCRIPTOR);
1053         dic.put(PdfName.ASCENT, new PdfNumber(os_2.sTypoAscender * 1000 / head.unitsPerEm));
1054         dic.put(PdfName.CAPHEIGHT, new PdfNumber(os_2.sCapHeight * 1000 / head.unitsPerEm));
1055         dic.put(PdfName.DESCENT, new PdfNumber(os_2.sTypoDescender * 1000 / head.unitsPerEm));
1056         dic.put(PdfName.FONTBBOX, new PdfRectangle(
1057         head.xMin * 1000 / head.unitsPerEm,
1058         head.yMin * 1000 / head.unitsPerEm,
1059         head.xMax * 1000 / head.unitsPerEm,
1060         head.yMax * 1000 / head.unitsPerEm));
1061         if (cidset != null)
1062             dic.put(PdfName.CIDSET, cidset);
1063         if (cff) {
1064             if (encoding.startsWith("Identity-"))
1065                 dic.put(PdfName.FONTNAME, new PdfName(subsetPrefix + fontName+"-"+encoding));
1066             else
1067                 dic.put(PdfName.FONTNAME, new PdfName(subsetPrefix + fontName + style));
1068         }
1069         else
1070             dic.put(PdfName.FONTNAME, new PdfName(subsetPrefix + fontName + style));
1071         dic.put(PdfName.ITALICANGLE, new PdfNumber(italicAngle));
1072         dic.put(PdfName.STEMV, new PdfNumber(80));
1073         if (fontStream != null) {
1074             if (cff)
1075                 dic.put(PdfName.FONTFILE3, fontStream);
1076             else
1077                 dic.put(PdfName.FONTFILE2, fontStream);
1078         }
1079         int flags = 0;
1080         if (isFixedPitch)
1081             flags |= 1;
1082         flags |= fontSpecific ? 4 : 32;
1083         if ((head.macStyle & 2) != 0)
1084             flags |= 64;
1085         if ((head.macStyle & 1) != 0)
1086             flags |= 262144;
1087         dic.put(PdfName.FLAGS, new PdfNumber(flags));
1088 
1089         return dic;
1090     }
1091 
1092     /** Generates the font dictionary for this font.
1093      * @return the PdfDictionary containing the font dictionary
1094      * @param subsetPrefix the subset prefix
1095      * @param firstChar the first valid character
1096      * @param lastChar the last valid character
1097      * @param shortTag a 256 bytes long <CODE>byte</CODE> array where each unused byte is represented by 0
1098      * @param fontDescriptor the indirect reference to a PdfDictionary containing the font descriptor or <CODE>null</CODE>
1099      */
getFontBaseType(PdfIndirectReference fontDescriptor, String subsetPrefix, int firstChar, int lastChar, byte shortTag[])1100     protected PdfDictionary getFontBaseType(PdfIndirectReference fontDescriptor, String subsetPrefix, int firstChar, int lastChar, byte shortTag[]) {
1101         PdfDictionary dic = new PdfDictionary(PdfName.FONT);
1102         if (cff) {
1103             dic.put(PdfName.SUBTYPE, PdfName.TYPE1);
1104             dic.put(PdfName.BASEFONT, new PdfName(fontName + style));
1105         }
1106         else {
1107             dic.put(PdfName.SUBTYPE, PdfName.TRUETYPE);
1108             dic.put(PdfName.BASEFONT, new PdfName(subsetPrefix + fontName + style));
1109         }
1110         dic.put(PdfName.BASEFONT, new PdfName(subsetPrefix + fontName + style));
1111         if (!fontSpecific) {
1112             for (int k = firstChar; k <= lastChar; ++k) {
1113                 if (!differences[k].equals(notdef)) {
1114                     firstChar = k;
1115                     break;
1116                 }
1117             }
1118         if (encoding.equals("Cp1252") || encoding.equals("MacRoman"))
1119                 dic.put(PdfName.ENCODING, encoding.equals("Cp1252") ? PdfName.WIN_ANSI_ENCODING : PdfName.MAC_ROMAN_ENCODING);
1120             else {
1121                 PdfDictionary enc = new PdfDictionary(PdfName.ENCODING);
1122                 PdfArray dif = new PdfArray();
1123                 boolean gap = true;
1124                 for (int k = firstChar; k <= lastChar; ++k) {
1125                     if (shortTag[k] != 0) {
1126                         if (gap) {
1127                             dif.add(new PdfNumber(k));
1128                             gap = false;
1129                         }
1130                         dif.add(new PdfName(differences[k]));
1131                     }
1132                     else
1133                         gap = true;
1134                 }
1135                 enc.put(PdfName.DIFFERENCES, dif);
1136                 dic.put(PdfName.ENCODING, enc);
1137             }
1138         }
1139         dic.put(PdfName.FIRSTCHAR, new PdfNumber(firstChar));
1140         dic.put(PdfName.LASTCHAR, new PdfNumber(lastChar));
1141         PdfArray wd = new PdfArray();
1142         for (int k = firstChar; k <= lastChar; ++k) {
1143             if (shortTag[k] == 0)
1144                 wd.add(new PdfNumber(0));
1145             else
1146                 wd.add(new PdfNumber(widths[k]));
1147         }
1148         dic.put(PdfName.WIDTHS, wd);
1149         if (fontDescriptor != null)
1150             dic.put(PdfName.FONTDESCRIPTOR, fontDescriptor);
1151         return dic;
1152     }
1153 
getFullFont()1154     protected byte[] getFullFont() throws IOException {
1155         RandomAccessFileOrArray rf2 = null;
1156         try {
1157             rf2 = new RandomAccessFileOrArray(rf);
1158             rf2.reOpen();
1159             byte b[] = new byte[rf2.length()];
1160             rf2.readFully(b);
1161             return b;
1162         }
1163         finally {
1164             try {if (rf2 != null) {rf2.close();}} catch (Exception e) {}
1165         }
1166     }
1167 
compactRanges(ArrayList ranges)1168     protected static int[] compactRanges(ArrayList ranges) {
1169         ArrayList simp = new ArrayList();
1170         for (int k = 0; k < ranges.size(); ++k) {
1171             int[] r = (int[])ranges.get(k);
1172             for (int j = 0; j < r.length; j += 2) {
1173                 simp.add(new int[]{Math.max(0, Math.min(r[j], r[j + 1])), Math.min(0xffff, Math.max(r[j], r[j + 1]))});
1174             }
1175         }
1176         for (int k1 = 0; k1 < simp.size() - 1; ++k1) {
1177             for (int k2 = k1 + 1; k2 < simp.size(); ++k2) {
1178                 int[] r1 = (int[])simp.get(k1);
1179                 int[] r2 = (int[])simp.get(k2);
1180                 if ((r1[0] >= r2[0] && r1[0] <= r2[1]) || (r1[1] >= r2[0] && r1[0] <= r2[1])) {
1181                     r1[0] = Math.min(r1[0], r2[0]);
1182                     r1[1] = Math.max(r1[1], r2[1]);
1183                     simp.remove(k2);
1184                     --k2;
1185                 }
1186             }
1187         }
1188         int[] s = new int[simp.size() * 2];
1189         for (int k = 0; k < simp.size(); ++k) {
1190             int[] r = (int[])simp.get(k);
1191             s[k * 2] = r[0];
1192             s[k * 2 + 1] = r[1];
1193         }
1194         return s;
1195     }
1196 
addRangeUni(HashMap longTag, boolean includeMetrics, boolean subsetp)1197     protected void addRangeUni(HashMap longTag, boolean includeMetrics, boolean subsetp) {
1198         if (!subsetp && (subsetRanges != null || directoryOffset > 0)) {
1199             int[] rg = (subsetRanges == null && directoryOffset > 0) ? new int[]{0, 0xffff} : compactRanges(subsetRanges);
1200             HashMap usemap;
1201             if (!fontSpecific && cmap31 != null)
1202                 usemap = cmap31;
1203             else if (fontSpecific && cmap10 != null)
1204                 usemap = cmap10;
1205             else if (cmap31 != null)
1206                 usemap = cmap31;
1207             else
1208                 usemap = cmap10;
1209             for (Iterator it = usemap.entrySet().iterator(); it.hasNext();) {
1210                 Map.Entry e = (Map.Entry)it.next();
1211                 int[] v = (int[])e.getValue();
1212                 Integer gi = new Integer(v[0]);
1213                 if (longTag.containsKey(gi))
1214                     continue;
1215                 int c = ((Integer)e.getKey()).intValue();
1216                 boolean skip = true;
1217                 for (int k = 0; k < rg.length; k += 2) {
1218                     if (c >= rg[k] && c <= rg[k + 1]) {
1219                         skip = false;
1220                         break;
1221                     }
1222                 }
1223                 if (!skip)
1224                     longTag.put(gi, includeMetrics ? new int[]{v[0], v[1], c} : null);
1225             }
1226         }
1227     }
1228 
1229     /** Outputs to the writer the font dictionaries and streams.
1230      * @param writer the writer for this document
1231      * @param ref the font indirect reference
1232      * @param params several parameters that depend on the font type
1233      * @throws IOException on error
1234      * @throws DocumentException error in generating the object
1235      */
writeFont(PdfWriter writer, PdfIndirectReference ref, Object params[])1236     void writeFont(PdfWriter writer, PdfIndirectReference ref, Object params[]) throws DocumentException, IOException {
1237         int firstChar = ((Integer)params[0]).intValue();
1238         int lastChar = ((Integer)params[1]).intValue();
1239         byte shortTag[] = (byte[])params[2];
1240         boolean subsetp = ((Boolean)params[3]).booleanValue() && subset;
1241 
1242         if (!subsetp) {
1243             firstChar = 0;
1244             lastChar = shortTag.length - 1;
1245             for (int k = 0; k < shortTag.length; ++k)
1246                 shortTag[k] = 1;
1247         }
1248         PdfIndirectReference ind_font = null;
1249         PdfObject pobj = null;
1250         PdfIndirectObject obj = null;
1251         String subsetPrefix = "";
1252         if (embedded) {
1253             if (cff) {
1254                 pobj = new StreamFont(readCffFont(), "Type1C", compressionLevel);
1255                 obj = writer.addToBody(pobj);
1256                 ind_font = obj.getIndirectReference();
1257             }
1258             else {
1259                 if (subsetp)
1260                     subsetPrefix = createSubsetPrefix();
1261                 HashMap glyphs = new HashMap();
1262                 for (int k = firstChar; k <= lastChar; ++k) {
1263                     if (shortTag[k] != 0) {
1264                         int[] metrics = null;
1265                         if (specialMap != null) {
1266                             int[] cd = GlyphList.nameToUnicode(differences[k]);
1267                             if (cd != null)
1268                                 metrics = getMetricsTT(cd[0]);
1269                         }
1270                         else {
1271                             if (fontSpecific)
1272                                 metrics = getMetricsTT(k);
1273                             else
1274                                 metrics = getMetricsTT(unicodeDifferences[k]);
1275                         }
1276                         if (metrics != null)
1277                             glyphs.put(new Integer(metrics[0]), null);
1278                     }
1279                 }
1280                 addRangeUni(glyphs, false, subsetp);
1281                 byte[] b = null;
1282                 if (subsetp || directoryOffset != 0 || subsetRanges != null) {
1283                     TrueTypeFontSubSet sb = new TrueTypeFontSubSet(fileName, new RandomAccessFileOrArray(rf), glyphs, directoryOffset, true, !subsetp);
1284                     b = sb.process();
1285                 }
1286                 else {
1287                     b = getFullFont();
1288                 }
1289                 int lengths[] = new int[]{b.length};
1290                 pobj = new StreamFont(b, lengths, compressionLevel);
1291                 obj = writer.addToBody(pobj);
1292                 ind_font = obj.getIndirectReference();
1293             }
1294         }
1295         pobj = getFontDescriptor(ind_font, subsetPrefix, null);
1296         if (pobj != null){
1297             obj = writer.addToBody(pobj);
1298             ind_font = obj.getIndirectReference();
1299         }
1300         pobj = getFontBaseType(ind_font, subsetPrefix, firstChar, lastChar, shortTag);
1301         writer.addToBody(pobj, ref);
1302     }
1303 
1304     /**
1305      * If this font file is using the Compact Font File Format, then this method
1306      * will return the raw bytes needed for the font stream. If this method is
1307      * ever made public: make sure to add a test if (cff == true).
1308      * @return	a byte array
1309      * @since	2.1.3
1310      */
readCffFont()1311     protected byte[] readCffFont() throws IOException {
1312         RandomAccessFileOrArray rf2 = new RandomAccessFileOrArray(rf);
1313         byte b[] = new byte[cffLength];
1314         try {
1315             rf2.reOpen();
1316             rf2.seek(cffOffset);
1317             rf2.readFully(b);
1318         }
1319         finally {
1320             try {
1321                 rf2.close();
1322             }
1323             catch (Exception e) {
1324                 // empty on purpose
1325             }
1326         }
1327     	return b;
1328     }
1329 
1330     /**
1331      * Returns a PdfStream object with the full font program.
1332      * @return	a PdfStream with the font program
1333      * @since	2.1.3
1334      */
getFullFontStream()1335     public PdfStream getFullFontStream() throws IOException, DocumentException {
1336         if (cff) {
1337             return new StreamFont(readCffFont(), "Type1C", compressionLevel);
1338         }
1339         else {
1340         	byte[] b = getFullFont();
1341         	int lengths[] = new int[]{b.length};
1342         	return new StreamFont(b, lengths, compressionLevel);
1343         }
1344     }
1345 
1346     /** Gets the font parameter identified by <CODE>key</CODE>. Valid values
1347      * for <CODE>key</CODE> are <CODE>ASCENT</CODE>, <CODE>CAPHEIGHT</CODE>, <CODE>DESCENT</CODE>
1348      * and <CODE>ITALICANGLE</CODE>.
1349      * @param key the parameter to be extracted
1350      * @param fontSize the font size in points
1351      * @return the parameter in points
1352      */
getFontDescriptor(int key, float fontSize)1353     public float getFontDescriptor(int key, float fontSize) {
1354         switch (key) {
1355             case ASCENT:
1356                 return os_2.sTypoAscender * fontSize / head.unitsPerEm;
1357             case CAPHEIGHT:
1358                 return os_2.sCapHeight * fontSize / head.unitsPerEm;
1359             case DESCENT:
1360                 return os_2.sTypoDescender * fontSize / head.unitsPerEm;
1361             case ITALICANGLE:
1362                 return (float)italicAngle;
1363             case BBOXLLX:
1364                 return fontSize * head.xMin / head.unitsPerEm;
1365             case BBOXLLY:
1366                 return fontSize * head.yMin / head.unitsPerEm;
1367             case BBOXURX:
1368                 return fontSize * head.xMax / head.unitsPerEm;
1369             case BBOXURY:
1370                 return fontSize * head.yMax / head.unitsPerEm;
1371             case AWT_ASCENT:
1372                 return fontSize * hhea.Ascender / head.unitsPerEm;
1373             case AWT_DESCENT:
1374                 return fontSize * hhea.Descender / head.unitsPerEm;
1375             case AWT_LEADING:
1376                 return fontSize * hhea.LineGap / head.unitsPerEm;
1377             case AWT_MAXADVANCE:
1378                 return fontSize * hhea.advanceWidthMax / head.unitsPerEm;
1379             case UNDERLINE_POSITION:
1380                 return (underlinePosition - underlineThickness / 2) * fontSize / head.unitsPerEm;
1381             case UNDERLINE_THICKNESS:
1382                 return underlineThickness * fontSize / head.unitsPerEm;
1383             case STRIKETHROUGH_POSITION:
1384                 return os_2.yStrikeoutPosition * fontSize / head.unitsPerEm;
1385             case STRIKETHROUGH_THICKNESS:
1386                 return os_2.yStrikeoutSize * fontSize / head.unitsPerEm;
1387             case SUBSCRIPT_SIZE:
1388                 return os_2.ySubscriptYSize * fontSize / head.unitsPerEm;
1389             case SUBSCRIPT_OFFSET:
1390                 return -os_2.ySubscriptYOffset * fontSize / head.unitsPerEm;
1391             case SUPERSCRIPT_SIZE:
1392                 return os_2.ySuperscriptYSize * fontSize / head.unitsPerEm;
1393             case SUPERSCRIPT_OFFSET:
1394                 return os_2.ySuperscriptYOffset * fontSize / head.unitsPerEm;
1395         }
1396         return 0;
1397     }
1398 
1399     /** Gets the glyph index and metrics for a character.
1400      * @param c the character
1401      * @return an <CODE>int</CODE> array with {glyph index, width}
1402      */
getMetricsTT(int c)1403     public int[] getMetricsTT(int c) {
1404         if (cmapExt != null)
1405             return (int[])cmapExt.get(new Integer(c));
1406         if (!fontSpecific && cmap31 != null)
1407             return (int[])cmap31.get(new Integer(c));
1408         if (fontSpecific && cmap10 != null)
1409             return (int[])cmap10.get(new Integer(c));
1410         if (cmap31 != null)
1411             return (int[])cmap31.get(new Integer(c));
1412         if (cmap10 != null)
1413             return (int[])cmap10.get(new Integer(c));
1414         return null;
1415     }
1416 
1417     /** Gets the postscript font name.
1418      * @return the postscript font name
1419      */
getPostscriptFontName()1420     public String getPostscriptFontName() {
1421         return fontName;
1422     }
1423 
1424     /** Gets the code pages supported by the font.
1425      * @return the code pages supported by the font
1426      */
getCodePagesSupported()1427     public String[] getCodePagesSupported() {
1428         long cp = (((long)os_2.ulCodePageRange2) << 32) + (os_2.ulCodePageRange1 & 0xffffffffL);
1429         int count = 0;
1430         long bit = 1;
1431         for (int k = 0; k < 64; ++k) {
1432             if ((cp & bit) != 0 && codePages[k] != null)
1433                 ++count;
1434             bit <<= 1;
1435         }
1436         String ret[] = new String[count];
1437         count = 0;
1438         bit = 1;
1439         for (int k = 0; k < 64; ++k) {
1440             if ((cp & bit) != 0 && codePages[k] != null)
1441                 ret[count++] = codePages[k];
1442             bit <<= 1;
1443         }
1444         return ret;
1445     }
1446 
1447     /** Gets the full name of the font. If it is a True Type font
1448      * each array element will have {Platform ID, Platform Encoding ID,
1449      * Language ID, font name}. The interpretation of this values can be
1450      * found in the Open Type specification, chapter 2, in the 'name' table.<br>
1451      * For the other fonts the array has a single element with {"", "", "",
1452      * font name}.
1453      * @return the full name of the font
1454      */
getFullFontName()1455     public String[][] getFullFontName() {
1456         return fullName;
1457     }
1458 
1459     /** Gets all the entries of the Names-Table. If it is a True Type font
1460      * each array element will have {Name ID, Platform ID, Platform Encoding ID,
1461      * Language ID, font name}. The interpretation of this values can be
1462      * found in the Open Type specification, chapter 2, in the 'name' table.<br>
1463      * For the other fonts the array has a single element with {"", "", "",
1464      * font name}.
1465      * @return the full name of the font
1466      */
getAllNameEntries()1467     public String[][] getAllNameEntries() {
1468         return allNameEntries;
1469     }
1470 
1471     /** Gets the family name of the font. If it is a True Type font
1472      * each array element will have {Platform ID, Platform Encoding ID,
1473      * Language ID, font name}. The interpretation of this values can be
1474      * found in the Open Type specification, chapter 2, in the 'name' table.<br>
1475      * For the other fonts the array has a single element with {"", "", "",
1476      * font name}.
1477      * @return the family name of the font
1478      */
getFamilyFontName()1479     public String[][] getFamilyFontName() {
1480         return familyName;
1481     }
1482 
1483     /** Checks if the font has any kerning pairs.
1484      * @return <CODE>true</CODE> if the font has any kerning pairs
1485      */
hasKernPairs()1486     public boolean hasKernPairs() {
1487         return kerning.size() > 0;
1488     }
1489 
1490     /**
1491      * Sets the font name that will appear in the pdf font dictionary.
1492      * Use with care as it can easily make a font unreadable if not embedded.
1493      * @param name the new font name
1494      */
setPostscriptFontName(String name)1495     public void setPostscriptFontName(String name) {
1496         fontName = name;
1497     }
1498 
1499     /**
1500      * Sets the kerning between two Unicode chars.
1501      * @param char1 the first char
1502      * @param char2 the second char
1503      * @param kern the kerning to apply in normalized 1000 units
1504      * @return <code>true</code> if the kerning was applied, <code>false</code> otherwise
1505      */
setKerning(int char1, int char2, int kern)1506     public boolean setKerning(int char1, int char2, int kern) {
1507         int metrics[] = getMetricsTT(char1);
1508         if (metrics == null)
1509             return false;
1510         int c1 = metrics[0];
1511         metrics = getMetricsTT(char2);
1512         if (metrics == null)
1513             return false;
1514         int c2 = metrics[0];
1515         kerning.put((c1 << 16) + c2, kern);
1516         return true;
1517     }
1518 
getRawCharBBox(int c, String name)1519     protected int[] getRawCharBBox(int c, String name) {
1520         HashMap map = null;
1521         if (name == null || cmap31 == null)
1522             map = cmap10;
1523         else
1524             map = cmap31;
1525         if (map == null)
1526             return null;
1527         int metric[] = (int[])map.get(new Integer(c));
1528         if (metric == null || bboxes == null)
1529             return null;
1530         return bboxes[metric[0]];
1531     }
1532 }
1533