1 /*
2 * $Id: TrueTypeFont.java 3527 2008-07-06 15:34:38Z blowagie $
3 *
4 * Copyright 2001-2006 Paulo Soares
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * (the "License"); you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the License.
13 *
14 * The Original Code is 'iText, a free JAVA-PDF library'.
15 *
16 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
17 * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
18 * All Rights Reserved.
19 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
20 * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
21 *
22 * Contributor(s): all the names of the contributors are added in the source code
23 * where applicable.
24 *
25 * Alternatively, the contents of this file may be used under the terms of the
26 * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
27 * provisions of LGPL are applicable instead of those above. If you wish to
28 * allow use of your version of this file only under the terms of the LGPL
29 * License and not to allow others to use your version of this file under
30 * the MPL, indicate your decision by deleting the provisions above and
31 * replace them with the notice and other provisions required by the LGPL.
32 * If you do not delete the provisions above, a recipient may use your version
33 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
34 *
35 * This library is free software; you can redistribute it and/or modify it
36 * under the terms of the MPL as stated above or under the terms of the GNU
37 * Library General Public License as published by the Free Software Foundation;
38 * either version 2 of the License, or any later version.
39 *
40 * This library is distributed in the hope that it will be useful, but WITHOUT
41 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
42 * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
43 * details.
44 *
45 * If you didn't download this code from the following link, you should check if
46 * you aren't using an obsolete version:
47 * http://www.lowagie.com/iText/
48 */
49
50 package com.lowagie.text.pdf;
51
52 import java.io.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
59 import com.lowagie.text.DocumentException;
60 import com.lowagie.text.ExceptionConverter;
61
62 /** Reads a Truetype font
63 *
64 * @author Paulo Soares (psoares@consiste.pt)
65 */
66 class TrueTypeFont extends BaseFont {
67
68 /** The code pages possible for a True Type font.
69 */
70 static final String codePages[] = {
71 "1252 Latin 1",
72 "1250 Latin 2: Eastern Europe",
73 "1251 Cyrillic",
74 "1253 Greek",
75 "1254 Turkish",
76 "1255 Hebrew",
77 "1256 Arabic",
78 "1257 Windows Baltic",
79 "1258 Vietnamese",
80 null,
81 null,
82 null,
83 null,
84 null,
85 null,
86 null,
87 "874 Thai",
88 "932 JIS/Japan",
89 "936 Chinese: Simplified chars--PRC and Singapore",
90 "949 Korean Wansung",
91 "950 Chinese: Traditional chars--Taiwan and Hong Kong",
92 "1361 Korean Johab",
93 null,
94 null,
95 null,
96 null,
97 null,
98 null,
99 null,
100 "Macintosh Character Set (US Roman)",
101 "OEM Character Set",
102 "Symbol Character Set",
103 null,
104 null,
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 "869 IBM Greek",
120 "866 MS-DOS Russian",
121 "865 MS-DOS Nordic",
122 "864 Arabic",
123 "863 MS-DOS Canadian French",
124 "862 Hebrew",
125 "861 MS-DOS Icelandic",
126 "860 MS-DOS Portuguese",
127 "857 IBM Turkish",
128 "855 IBM Cyrillic; primarily Russian",
129 "852 Latin 2",
130 "775 MS-DOS Baltic",
131 "737 Greek; former 437 G",
132 "708 Arabic; ASMO 708",
133 "850 WE/Latin 1",
134 "437 US"};
135
136 protected boolean justNames = false;
137 /** Contains the location of the several tables. The key is the name of
138 * the table and the value is an <CODE>int[2]</CODE> where position 0
139 * is the offset from the start of the file and position 1 is the length
140 * of the table.
141 */
142 protected HashMap tables;
143 /** The file in use.
144 */
145 protected RandomAccessFileOrArray rf;
146 /** The file name.
147 */
148 protected String fileName;
149
150 protected boolean cff = false;
151
152 protected int cffOffset;
153
154 protected int cffLength;
155
156 /** The offset from the start of the file to the table directory.
157 * It is 0 for TTF and may vary for TTC depending on the chosen font.
158 */
159 protected int directoryOffset;
160 /** The index for the TTC font. It is an empty <CODE>String</CODE> for a
161 * TTF file.
162 */
163 protected String ttcIndex;
164 /** The style modifier */
165 protected String style = "";
166 /** The content of table 'head'.
167 */
168 protected FontHeader head = new FontHeader();
169 /** The content of table 'hhea'.
170 */
171 protected HorizontalHeader hhea = new HorizontalHeader();
172 /** The content of table 'OS/2'.
173 */
174 protected WindowsMetrics os_2 = new WindowsMetrics();
175 /** The width of the glyphs. This is essentially the content of table
176 * 'hmtx' normalized to 1000 units.
177 */
178 protected int GlyphWidths[];
179
180 protected int bboxes[][];
181 /** The map containing the code information for the table 'cmap', encoding 1.0.
182 * The key is the code and the value is an <CODE>int[2]</CODE> where position 0
183 * is the glyph number and position 1 is the glyph width normalized to 1000
184 * units.
185 */
186 protected HashMap cmap10;
187 /** The map containing the code information for the table 'cmap', encoding 3.1
188 * in Unicode.
189 * <P>
190 * The key is the code and the value is an <CODE>int</CODE>[2] where position 0
191 * is the glyph number and position 1 is the glyph width normalized to 1000
192 * units.
193 */
194 protected HashMap cmap31;
195
196 protected HashMap cmapExt;
197
198 /** The map containing the kerning information. It represents the content of
199 * table 'kern'. The key is an <CODE>Integer</CODE> where the top 16 bits
200 * are the glyph number for the first character and the lower 16 bits are the
201 * glyph number for the second character. The value is the amount of kerning in
202 * normalized 1000 units as an <CODE>Integer</CODE>. This value is usually negative.
203 */
204 protected IntHashtable kerning = new IntHashtable();
205 /**
206 * The font name.
207 * This name is usually extracted from the table 'name' with
208 * the 'Name ID' 6.
209 */
210 protected String fontName;
211
212 /** The full name of the font
213 */
214 protected String fullName[][];
215
216 /** All the names of the Names-Table
217 */
218 protected String allNameEntries[][];
219
220 /** The family name of the font
221 */
222 protected String familyName[][];
223 /** The italic angle. It is usually extracted from the 'post' table or in it's
224 * absence with the code:
225 * <P>
226 * <PRE>
227 * -Math.atan2(hhea.caretSlopeRun, hhea.caretSlopeRise) * 180 / Math.PI
228 * </PRE>
229 */
230 protected double italicAngle;
231 /** <CODE>true</CODE> if all the glyphs have the same width.
232 */
233 protected boolean isFixedPitch = false;
234
235 protected int underlinePosition;
236
237 protected int underlineThickness;
238
239 /** The components of table 'head'.
240 */
241 protected static class FontHeader {
242 /** A variable. */
243 int flags;
244 /** A variable. */
245 int unitsPerEm;
246 /** A variable. */
247 short xMin;
248 /** A variable. */
249 short yMin;
250 /** A variable. */
251 short xMax;
252 /** A variable. */
253 short yMax;
254 /** A variable. */
255 int macStyle;
256 }
257
258 /** The components of table 'hhea'.
259 */
260 protected static class HorizontalHeader {
261 /** A variable. */
262 short Ascender;
263 /** A variable. */
264 short Descender;
265 /** A variable. */
266 short LineGap;
267 /** A variable. */
268 int advanceWidthMax;
269 /** A variable. */
270 short minLeftSideBearing;
271 /** A variable. */
272 short minRightSideBearing;
273 /** A variable. */
274 short xMaxExtent;
275 /** A variable. */
276 short caretSlopeRise;
277 /** A variable. */
278 short caretSlopeRun;
279 /** A variable. */
280 int numberOfHMetrics;
281 }
282
283 /** The components of table 'OS/2'.
284 */
285 protected static class WindowsMetrics {
286 /** A variable. */
287 short xAvgCharWidth;
288 /** A variable. */
289 int usWeightClass;
290 /** A variable. */
291 int usWidthClass;
292 /** A variable. */
293 short fsType;
294 /** A variable. */
295 short ySubscriptXSize;
296 /** A variable. */
297 short ySubscriptYSize;
298 /** A variable. */
299 short ySubscriptXOffset;
300 /** A variable. */
301 short ySubscriptYOffset;
302 /** A variable. */
303 short ySuperscriptXSize;
304 /** A variable. */
305 short ySuperscriptYSize;
306 /** A variable. */
307 short ySuperscriptXOffset;
308 /** A variable. */
309 short ySuperscriptYOffset;
310 /** A variable. */
311 short yStrikeoutSize;
312 /** A variable. */
313 short yStrikeoutPosition;
314 /** A variable. */
315 short sFamilyClass;
316 /** A variable. */
317 byte panose[] = new byte[10];
318 /** A variable. */
319 byte achVendID[] = new byte[4];
320 /** A variable. */
321 int fsSelection;
322 /** A variable. */
323 int usFirstCharIndex;
324 /** A variable. */
325 int usLastCharIndex;
326 /** A variable. */
327 short sTypoAscender;
328 /** A variable. */
329 short sTypoDescender;
330 /** A variable. */
331 short sTypoLineGap;
332 /** A variable. */
333 int usWinAscent;
334 /** A variable. */
335 int usWinDescent;
336 /** A variable. */
337 int ulCodePageRange1;
338 /** A variable. */
339 int ulCodePageRange2;
340 /** A variable. */
341 int sCapHeight;
342 }
343
344 /** This constructor is present to allow extending the class.
345 */
346 protected TrueTypeFont() {
347 }
348
349 TrueTypeFont(String ttFile, String enc, boolean emb, byte ttfAfm[]) throws DocumentException, IOException {
350 this(ttFile, enc, emb, ttfAfm, false);
351 }
352
353 /** Creates a new TrueType font.
354 * @param ttFile the location of the font on file. The file must end in '.ttf' or
355 * '.ttc' but can have modifiers after the name
356 * @param enc the encoding to be applied to this font
357 * @param emb true if the font is to be embedded in the PDF
358 * @param ttfAfm the font as a <CODE>byte</CODE> array
359 * @throws DocumentException the font is invalid
360 * @throws IOException the font file could not be read
361 */
362 TrueTypeFont(String ttFile, String enc, boolean emb, byte ttfAfm[], boolean justNames) throws DocumentException, IOException {
363 this.justNames = justNames;
364 String nameBase = getBaseName(ttFile);
365 String ttcName = getTTCName(nameBase);
366 if (nameBase.length() < ttFile.length()) {
367 style = ttFile.substring(nameBase.length());
368 }
369 encoding = enc;
370 embedded = emb;
371 fileName = ttcName;
372 fontType = FONT_TYPE_TT;
373 ttcIndex = "";
374 if (ttcName.length() < nameBase.length())
375 ttcIndex = nameBase.substring(ttcName.length() + 1);
376 if (fileName.toLowerCase().endsWith(".ttf") || fileName.toLowerCase().endsWith(".otf") || fileName.toLowerCase().endsWith(".ttc")) {
377 process(ttfAfm);
378 if (!justNames && embedded && os_2.fsType == 2)
379 throw new DocumentException(fileName + style + " cannot be embedded due to licensing restrictions.");
380 }
381 else
382 throw new DocumentException(fileName + style + " is not a TTF, OTF or TTC font file.");
383 if (!encoding.startsWith("#"))
384 PdfEncodings.convertToBytes(" ", enc); // check if the encoding exists
385 createEncoding();
386 }
387
388 /** Gets the name from a composed TTC file name.
389 * If I have for input "myfont.ttc,2" the return will
390 * be "myfont.ttc".
391 * @param name the full name
392 * @return the simple file name
393 */
394 protected static String getTTCName(String name) {
395 int idx = name.toLowerCase().indexOf(".ttc,");
396 if (idx < 0)
397 return name;
398 else
399 return name.substring(0, idx + 4);
400 }
401
402
403 /**
404 * Reads the tables 'head', 'hhea', 'OS/2' and 'post' filling several variables.
405 * @throws DocumentException the font is invalid
406 * @throws IOException the font file could not be read
407 */
408 void fillTables() throws DocumentException, IOException {
409 int table_location[];
410 table_location = (int[])tables.get("head");
411 if (table_location == null)
412 throw new DocumentException("Table 'head' does not exist in " + fileName + style);
413 rf.seek(table_location[0] + 16);
414 head.flags = rf.readUnsignedShort();
415 head.unitsPerEm = rf.readUnsignedShort();
416 rf.skipBytes(16);
417 head.xMin = rf.readShort();
418 head.yMin = rf.readShort();
419 head.xMax = rf.readShort();
420 head.yMax = rf.readShort();
421 head.macStyle = rf.readUnsignedShort();
422
423 table_location = (int[])tables.get("hhea");
424 if (table_location == null)
425 throw new DocumentException("Table 'hhea' does not exist " + fileName + style);
426 rf.seek(table_location[0] + 4);
427 hhea.Ascender = rf.readShort();
428 hhea.Descender = rf.readShort();
429 hhea.LineGap = rf.readShort();
430 hhea.advanceWidthMax = rf.readUnsignedShort();
431 hhea.minLeftSideBearing = rf.readShort();
432 hhea.minRightSideBearing = rf.readShort();
433 hhea.xMaxExtent = rf.readShort();
434 hhea.caretSlopeRise = rf.readShort();
435 hhea.caretSlopeRun = rf.readShort();
436 rf.skipBytes(12);
437 hhea.numberOfHMetrics = rf.readUnsignedShort();
438
439 table_location = (int[])tables.get("OS/2");
440 if (table_location == null)
441 throw new DocumentException("Table 'OS/2' does not exist in " + fileName + style);
442 rf.seek(table_location[0]);
443 int version = rf.readUnsignedShort();
444 os_2.xAvgCharWidth = rf.readShort();
445 os_2.usWeightClass = rf.readUnsignedShort();
446 os_2.usWidthClass = rf.readUnsignedShort();
447 os_2.fsType = rf.readShort();
448 os_2.ySubscriptXSize = rf.readShort();
449 os_2.ySubscriptYSize = rf.readShort();
450 os_2.ySubscriptXOffset = rf.readShort();
451 os_2.ySubscriptYOffset = rf.readShort();
452 os_2.ySuperscriptXSize = rf.readShort();
453 os_2.ySuperscriptYSize = rf.readShort();
454 os_2.ySuperscriptXOffset = rf.readShort();
455 os_2.ySuperscriptYOffset = rf.readShort();
456 os_2.yStrikeoutSize = rf.readShort();
457 os_2.yStrikeoutPosition = rf.readShort();
458 os_2.sFamilyClass = rf.readShort();
459 rf.readFully(os_2.panose);
460 rf.skipBytes(16);
461 rf.readFully(os_2.achVendID);
462 os_2.fsSelection = rf.readUnsignedShort();
463 os_2.usFirstCharIndex = rf.readUnsignedShort();
464 os_2.usLastCharIndex = rf.readUnsignedShort();
465 os_2.sTypoAscender = rf.readShort();
466 os_2.sTypoDescender = rf.readShort();
467 if (os_2.sTypoDescender > 0)
468 os_2.sTypoDescender = (short)(-os_2.sTypoDescender);
469 os_2.sTypoLineGap = rf.readShort();
470 os_2.usWinAscent = rf.readUnsignedShort();
471 os_2.usWinDescent = rf.readUnsignedShort();
472 os_2.ulCodePageRange1 = 0;
473 os_2.ulCodePageRange2 = 0;
474 if (version > 0) {
475 os_2.ulCodePageRange1 = rf.readInt();
476 os_2.ulCodePageRange2 = rf.readInt();
477 }
478 if (version > 1) {
479 rf.skipBytes(2);
480 os_2.sCapHeight = rf.readShort();
481 }
482 else
483 os_2.sCapHeight = (int)(0.7 * head.unitsPerEm);
484
485 table_location = (int[])tables.get("post");
486 if (table_location == null) {
487 italicAngle = -Math.atan2(hhea.caretSlopeRun, hhea.caretSlopeRise) * 180 / Math.PI;
488 return;
489 }
490 rf.seek(table_location[0] + 4);
491 short mantissa = rf.readShort();
492 int fraction = rf.readUnsignedShort();
493 italicAngle = mantissa + fraction / 16384.0d;
494 underlinePosition = rf.readShort();
495 underlineThickness = rf.readShort();
496 isFixedPitch = rf.readInt() != 0;
497 }
498
499 /**
500 * Gets the Postscript font name.
501 * @throws DocumentException the font is invalid
502 * @throws IOException the font file could not be read
503 * @return the Postscript font name
504 */
505 String getBaseFont() throws DocumentException, IOException {
506 int table_location[];
507 table_location = (int[])tables.get("name");
508 if (table_location == null)
509 throw new DocumentException("Table 'name' does not exist in " + fileName + style);
510 rf.seek(table_location[0] + 2);
511 int numRecords = rf.readUnsignedShort();
512 int startOfStorage = rf.readUnsignedShort();
513 for (int k = 0; k < numRecords; ++k) {
514 int platformID = rf.readUnsignedShort();
515 int platformEncodingID = rf.readUnsignedShort();
516 int languageID = rf.readUnsignedShort();
517 int nameID = rf.readUnsignedShort();
518 int length = rf.readUnsignedShort();
519 int offset = rf.readUnsignedShort();
520 if (nameID == 6) {
521 rf.seek(table_location[0] + startOfStorage + offset);
522 if (platformID == 0 || platformID == 3)
523 return readUnicodeString(length);
524 else
525 return readStandardString(length);
526 }
527 }
528 File file = new File(fileName);
529 return file.getName().replace(' ', '-');
530 }
531
532 /** Extracts the names of the font in all the languages available.
533 * @param id the name id to retrieve
534 * @throws DocumentException on error
535 * @throws IOException on error
536 */
537 String[][] getNames(int id) throws DocumentException, IOException {
538 int table_location[];
539 table_location = (int[])tables.get("name");
540 if (table_location == null)
541 throw new DocumentException("Table 'name' does not exist in " + fileName + style);
542 rf.seek(table_location[0] + 2);
543 int numRecords = rf.readUnsignedShort();
544 int startOfStorage = rf.readUnsignedShort();
545 ArrayList names = new ArrayList();
546 for (int k = 0; k < numRecords; ++k) {
547 int platformID = rf.readUnsignedShort();
548 int platformEncodingID = rf.readUnsignedShort();
549 int languageID = rf.readUnsignedShort();
550 int nameID = rf.readUnsignedShort();
551 int length = rf.readUnsignedShort();
552 int offset = rf.readUnsignedShort();
553 if (nameID == id) {
554 int pos = rf.getFilePointer();
555 rf.seek(table_location[0] + startOfStorage + offset);
556 String name;
557 if (platformID == 0 || platformID == 3 || (platformID == 2 && platformEncodingID == 1)){
558 name = readUnicodeString(length);
559 }
560 else {
561 name = readStandardString(length);
562 }
563 names.add(new String[]{String.valueOf(platformID),
564 String.valueOf(platformEncodingID), String.valueOf(languageID), name});
565 rf.seek(pos);
566 }
567 }
568 String thisName[][] = new String[names.size()][];
569 for (int k = 0; k < names.size(); ++k)
570 thisName[k] = (String[])names.get(k);
571 return thisName;
572 }
573
574 /** Extracts all the names of the names-Table
575 * @throws DocumentException on error
576 * @throws IOException on error
577 */
578 String[][] getAllNames() throws DocumentException, IOException {
579 int table_location[];
580 table_location = (int[])tables.get("name");
581 if (table_location == null)
582 throw new DocumentException("Table 'name' does not exist in " + fileName + style);
583 rf.seek(table_location[0] + 2);
584 int numRecords = rf.readUnsignedShort();
585 int startOfStorage = rf.readUnsignedShort();
586 ArrayList names = new ArrayList();
587 for (int k = 0; k < numRecords; ++k) {
588 int platformID = rf.readUnsignedShort();
589 int platformEncodingID = rf.readUnsignedShort();
590 int languageID = rf.readUnsignedShort();
591 int nameID = rf.readUnsignedShort();
592 int length = rf.readUnsignedShort();
593 int offset = rf.readUnsignedShort();
594 int pos = rf.getFilePointer();
595 rf.seek(table_location[0] + startOfStorage + offset);
596 String name;
597 if (platformID == 0 || platformID == 3 || (platformID == 2 && platformEncodingID == 1)){
598 name = readUnicodeString(length);
599 }
600 else {
601 name = readStandardString(length);
602 }
603 names.add(new String[]{String.valueOf(nameID), String.valueOf(platformID),
604 String.valueOf(platformEncodingID), String.valueOf(languageID), name});
605 rf.seek(pos);
606 }
607 String thisName[][] = new String[names.size()][];
608 for (int k = 0; k < names.size(); ++k)
609 thisName[k] = (String[])names.get(k);
610 return thisName;
611 }
612
613 void checkCff() {
614 int table_location[];
615 table_location = (int[])tables.get("CFF ");
616 if (table_location != null) {
617 cff = true;
618 cffOffset = table_location[0];
619 cffLength = table_location[1];
620 }
621 }
622
623 /** Reads the font data.
624 * @param ttfAfm the font as a <CODE>byte</CODE> array, possibly <CODE>null</CODE>
625 * @throws DocumentException the font is invalid
626 * @throws IOException the font file could not be read
627 */
628 void process(byte ttfAfm[]) throws DocumentException, IOException {
629 tables = new HashMap();
630
631 try {
632 if (ttfAfm == null)
633 rf = new RandomAccessFileOrArray(fileName);
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("The font index for " + fileName + " must be positive.");
640 String mainTag = readStandardString(4);
641 if (!mainTag.equals("ttcf"))
642 throw new DocumentException(fileName + " is not a valid TTC file.");
643 rf.skipBytes(4);
644 int dirCount = rf.readInt();
645 if (dirIdx >= dirCount)
646 throw new DocumentException("The font index for " + fileName + " must be between 0 and " + (dirCount - 1) + ". It was " + 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(fileName + " is not a valid TTF or OTF file.");
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 */
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 */
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 */
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("Table 'hmtx' does not exist in " + 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 */
742 protected int getGlyphWidth(int glyph) {
743 if (glyph >= GlyphWidths.length)
744 glyph = GlyphWidths.length - 1;
745 return GlyphWidths[glyph];
746 }
747
748 private void readBbox() throws DocumentException, IOException {
749 int tableLocation[];
750 tableLocation = (int[])tables.get("head");
751 if (tableLocation == null)
752 throw new DocumentException("Table 'head' does not exist in " + 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("Table 'glyf' does not exist in " + 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 */
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("Table 'cmap' does not exist in " + 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
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 */
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 */
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 */
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 */
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 */
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 */
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 * @throws DocumentException if there is an error
1051 */
1052 protected PdfDictionary getFontDescriptor(PdfIndirectReference fontStream, String subsetPrefix, PdfIndirectReference cidset) {
1053 PdfDictionary dic = new PdfDictionary(PdfName.FONTDESCRIPTOR);
1054 dic.put(PdfName.ASCENT, new PdfNumber(os_2.sTypoAscender * 1000 / head.unitsPerEm));
1055 dic.put(PdfName.CAPHEIGHT, new PdfNumber(os_2.sCapHeight * 1000 / head.unitsPerEm));
1056 dic.put(PdfName.DESCENT, new PdfNumber(os_2.sTypoDescender * 1000 / head.unitsPerEm));
1057 dic.put(PdfName.FONTBBOX, new PdfRectangle(
1058 head.xMin * 1000 / head.unitsPerEm,
1059 head.yMin * 1000 / head.unitsPerEm,
1060 head.xMax * 1000 / head.unitsPerEm,
1061 head.yMax * 1000 / head.unitsPerEm));
1062 if (cidset != null)
1063 dic.put(PdfName.CIDSET, cidset);
1064 if (cff) {
1065 if (encoding.startsWith("Identity-"))
1066 dic.put(PdfName.FONTNAME, new PdfName(subsetPrefix + fontName+"-"+encoding));
1067 else
1068 dic.put(PdfName.FONTNAME, new PdfName(subsetPrefix + fontName + style));
1069 }
1070 else
1071 dic.put(PdfName.FONTNAME, new PdfName(subsetPrefix + fontName + style));
1072 dic.put(PdfName.ITALICANGLE, new PdfNumber(italicAngle));
1073 dic.put(PdfName.STEMV, new PdfNumber(80));
1074 if (fontStream != null) {
1075 if (cff)
1076 dic.put(PdfName.FONTFILE3, fontStream);
1077 else
1078 dic.put(PdfName.FONTFILE2, fontStream);
1079 }
1080 int flags = 0;
1081 if (isFixedPitch)
1082 flags |= 1;
1083 flags |= fontSpecific ? 4 : 32;
1084 if ((head.macStyle & 2) != 0)
1085 flags |= 64;
1086 if ((head.macStyle & 1) != 0)
1087 flags |= 262144;
1088 dic.put(PdfName.FLAGS, new PdfNumber(flags));
1089
1090 return dic;
1091 }
1092
1093 /** Generates the font dictionary for this font.
1094 * @return the PdfDictionary containing the font dictionary
1095 * @param subsetPrefix the subset prefx
1096 * @param firstChar the first valid character
1097 * @param lastChar the last valid character
1098 * @param shortTag a 256 bytes long <CODE>byte</CODE> array where each unused byte is represented by 0
1099 * @param fontDescriptor the indirect reference to a PdfDictionary containing the font descriptor or <CODE>null</CODE>
1100 * @throws DocumentException if there is an error
1101 */
1102 protected PdfDictionary getFontBaseType(PdfIndirectReference fontDescriptor, String subsetPrefix, int firstChar, int lastChar, byte shortTag[]) {
1103 PdfDictionary dic = new PdfDictionary(PdfName.FONT);
1104 if (cff) {
1105 dic.put(PdfName.SUBTYPE, PdfName.TYPE1);
1106 dic.put(PdfName.BASEFONT, new PdfName(fontName + style));
1107 }
1108 else {
1109 dic.put(PdfName.SUBTYPE, PdfName.TRUETYPE);
1110 dic.put(PdfName.BASEFONT, new PdfName(subsetPrefix + fontName + style));
1111 }
1112 dic.put(PdfName.BASEFONT, new PdfName(subsetPrefix + fontName + style));
1113 if (!fontSpecific) {
1114 for (int k = firstChar; k <= lastChar; ++k) {
1115 if (!differences[k].equals(notdef)) {
1116 firstChar = k;
1117 break;
1118 }
1119 }
1120 if (encoding.equals("Cp1252") || encoding.equals("MacRoman"))
1121 dic.put(PdfName.ENCODING, encoding.equals("Cp1252") ? PdfName.WIN_ANSI_ENCODING : PdfName.MAC_ROMAN_ENCODING);
1122 else {
1123 PdfDictionary enc = new PdfDictionary(PdfName.ENCODING);
1124 PdfArray dif = new PdfArray();
1125 boolean gap = true;
1126 for (int k = firstChar; k <= lastChar; ++k) {
1127 if (shortTag[k] != 0) {
1128 if (gap) {
1129 dif.add(new PdfNumber(k));
1130 gap = false;
1131 }
1132 dif.add(new PdfName(differences[k]));
1133 }
1134 else
1135 gap = true;
1136 }
1137 enc.put(PdfName.DIFFERENCES, dif);
1138 dic.put(PdfName.ENCODING, enc);
1139 }
1140 }
1141 dic.put(PdfName.FIRSTCHAR, new PdfNumber(firstChar));
1142 dic.put(PdfName.LASTCHAR, new PdfNumber(lastChar));
1143 PdfArray wd = new PdfArray();
1144 for (int k = firstChar; k <= lastChar; ++k) {
1145 if (shortTag[k] == 0)
1146 wd.add(new PdfNumber(0));
1147 else
1148 wd.add(new PdfNumber(widths[k]));
1149 }
1150 dic.put(PdfName.WIDTHS, wd);
1151 if (fontDescriptor != null)
1152 dic.put(PdfName.FONTDESCRIPTOR, fontDescriptor);
1153 return dic;
1154 }
1155
1156 protected byte[] getFullFont() throws IOException {
1157 RandomAccessFileOrArray rf2 = null;
1158 try {
1159 rf2 = new RandomAccessFileOrArray(rf);
1160 rf2.reOpen();
1161 byte b[] = new byte[rf2.length()];
1162 rf2.readFully(b);
1163 return b;
1164 }
1165 finally {
1166 try {if (rf2 != null) {rf2.close();}} catch (Exception e) {}
1167 }
1168 }
1169
1170 protected static int[] compactRanges(ArrayList ranges) {
1171 ArrayList simp = new ArrayList();
1172 for (int k = 0; k < ranges.size(); ++k) {
1173 int[] r = (int[])ranges.get(k);
1174 for (int j = 0; j < r.length; j += 2) {
1175 simp.add(new int[]{Math.max(0, Math.min(r[j], r[j + 1])), Math.min(0xffff, Math.max(r[j], r[j + 1]))});
1176 }
1177 }
1178 for (int k1 = 0; k1 < simp.size() - 1; ++k1) {
1179 for (int k2 = k1 + 1; k2 < simp.size(); ++k2) {
1180 int[] r1 = (int[])simp.get(k1);
1181 int[] r2 = (int[])simp.get(k2);
1182 if ((r1[0] >= r2[0] && r1[0] <= r2[1]) || (r1[1] >= r2[0] && r1[0] <= r2[1])) {
1183 r1[0] = Math.min(r1[0], r2[0]);
1184 r1[1] = Math.max(r1[1], r2[1]);
1185 simp.remove(k2);
1186 --k2;
1187 }
1188 }
1189 }
1190 int[] s = new int[simp.size() * 2];
1191 for (int k = 0; k < simp.size(); ++k) {
1192 int[] r = (int[])simp.get(k);
1193 s[k * 2] = r[0];
1194 s[k * 2 + 1] = r[1];
1195 }
1196 return s;
1197 }
1198
1199 protected void addRangeUni(HashMap longTag, boolean includeMetrics, boolean subsetp) {
1200 if (!subsetp && (subsetRanges != null || directoryOffset > 0)) {
1201 int[] rg = (subsetRanges == null && directoryOffset > 0) ? new int[]{0, 0xffff} : compactRanges(subsetRanges);
1202 HashMap usemap;
1203 if (!fontSpecific && cmap31 != null)
1204 usemap = cmap31;
1205 else if (fontSpecific && cmap10 != null)
1206 usemap = cmap10;
1207 else if (cmap31 != null)
1208 usemap = cmap31;
1209 else
1210 usemap = cmap10;
1211 for (Iterator it = usemap.entrySet().iterator(); it.hasNext();) {
1212 Map.Entry e = (Map.Entry)it.next();
1213 int[] v = (int[])e.getValue();
1214 Integer gi = new Integer(v[0]);
1215 if (longTag.containsKey(gi))
1216 continue;
1217 int c = ((Integer)e.getKey()).intValue();
1218 boolean skip = true;
1219 for (int k = 0; k < rg.length; k += 2) {
1220 if (c >= rg[k] && c <= rg[k + 1]) {
1221 skip = false;
1222 break;
1223 }
1224 }
1225 if (!skip)
1226 longTag.put(gi, includeMetrics ? new int[]{v[0], v[1], c} : null);
1227 }
1228 }
1229 }
1230
1231 /** Outputs to the writer the font dictionaries and streams.
1232 * @param writer the writer for this document
1233 * @param ref the font indirect reference
1234 * @param params several parameters that depend on the font type
1235 * @throws IOException on error
1236 * @throws DocumentException error in generating the object
1237 */
1238 void writeFont(PdfWriter writer, PdfIndirectReference ref, Object params[]) throws DocumentException, IOException {
1239 int firstChar = ((Integer)params[0]).intValue();
1240 int lastChar = ((Integer)params[1]).intValue();
1241 byte shortTag[] = (byte[])params[2];
1242 boolean subsetp = ((Boolean)params[3]).booleanValue() && subset;
1243
1244 if (!subsetp) {
1245 firstChar = 0;
1246 lastChar = shortTag.length - 1;
1247 for (int k = 0; k < shortTag.length; ++k)
1248 shortTag[k] = 1;
1249 }
1250 PdfIndirectReference ind_font = null;
1251 PdfObject pobj = null;
1252 PdfIndirectObject obj = null;
1253 String subsetPrefix = "";
1254 if (embedded) {
1255 if (cff) {
1256 pobj = new StreamFont(readCffFont(), "Type1C", compressionLevel);
1257 obj = writer.addToBody(pobj);
1258 ind_font = obj.getIndirectReference();
1259 }
1260 else {
1261 if (subsetp)
1262 subsetPrefix = createSubsetPrefix();
1263 HashMap glyphs = new HashMap();
1264 for (int k = firstChar; k <= lastChar; ++k) {
1265 if (shortTag[k] != 0) {
1266 int[] metrics = null;
1267 if (specialMap != null) {
1268 int[] cd = GlyphList.nameToUnicode(differences[k]);
1269 if (cd != null)
1270 metrics = getMetricsTT(cd[0]);
1271 }
1272 else {
1273 if (fontSpecific)
1274 metrics = getMetricsTT(k);
1275 else
1276 metrics = getMetricsTT(unicodeDifferences[k]);
1277 }
1278 if (metrics != null)
1279 glyphs.put(new Integer(metrics[0]), null);
1280 }
1281 }
1282 addRangeUni(glyphs, false, subsetp);
1283 byte[] b = null;
1284 if (subsetp || directoryOffset != 0 || subsetRanges != null) {
1285 TrueTypeFontSubSet sb = new TrueTypeFontSubSet(fileName, new RandomAccessFileOrArray(rf), glyphs, directoryOffset, true, !subsetp);
1286 b = sb.process();
1287 }
1288 else {
1289 b = getFullFont();
1290 }
1291 int lengths[] = new int[]{b.length};
1292 pobj = new StreamFont(b, lengths, compressionLevel);
1293 obj = writer.addToBody(pobj);
1294 ind_font = obj.getIndirectReference();
1295 }
1296 }
1297 pobj = getFontDescriptor(ind_font, subsetPrefix, null);
1298 if (pobj != null){
1299 obj = writer.addToBody(pobj);
1300 ind_font = obj.getIndirectReference();
1301 }
1302 pobj = getFontBaseType(ind_font, subsetPrefix, firstChar, lastChar, shortTag);
1303 writer.addToBody(pobj, ref);
1304 }
1305
1306 /**
1307 * If this font file is using the Compact Font File Format, then this method
1308 * will return the raw bytes needed for the font stream. If this method is
1309 * ever made public: make sure to add a test if (cff == true).
1310 * @return a byte array
1311 * @since 2.1.3
1312 */
1313 protected byte[] readCffFont() throws IOException {
1314 RandomAccessFileOrArray rf2 = new RandomAccessFileOrArray(rf);
1315 byte b[] = new byte[cffLength];
1316 try {
1317 rf2.reOpen();
1318 rf2.seek(cffOffset);
1319 rf2.readFully(b);
1320 }
1321 finally {
1322 try {
1323 rf2.close();
1324 }
1325 catch (Exception e) {
1326 // empty on purpose
1327 }
1328 }
1329 return b;
1330 }
1331
1332 /**
1333 * Returns a PdfStream object with the full font program.
1334 * @return a PdfStream with the font program
1335 * @since 2.1.3
1336 */
1337 public PdfStream getFullFontStream() throws IOException, DocumentException {
1338 if (cff) {
1339 return new StreamFont(readCffFont(), "Type1C", compressionLevel);
1340 }
1341 else {
1342 byte[] b = getFullFont();
1343 int lengths[] = new int[]{b.length};
1344 return new StreamFont(b, lengths, compressionLevel);
1345 }
1346 }
1347
1348 /** Gets the font parameter identified by <CODE>key</CODE>. Valid values
1349 * for <CODE>key</CODE> are <CODE>ASCENT</CODE>, <CODE>CAPHEIGHT</CODE>, <CODE>DESCENT</CODE>
1350 * and <CODE>ITALICANGLE</CODE>.
1351 * @param key the parameter to be extracted
1352 * @param fontSize the font size in points
1353 * @return the parameter in points
1354 */
1355 public float getFontDescriptor(int key, float fontSize) {
1356 switch (key) {
1357 case ASCENT:
1358 return os_2.sTypoAscender * fontSize / head.unitsPerEm;
1359 case CAPHEIGHT:
1360 return os_2.sCapHeight * fontSize / head.unitsPerEm;
1361 case DESCENT:
1362 return os_2.sTypoDescender * fontSize / head.unitsPerEm;
1363 case ITALICANGLE:
1364 return (float)italicAngle;
1365 case BBOXLLX:
1366 return fontSize * head.xMin / head.unitsPerEm;
1367 case BBOXLLY:
1368 return fontSize * head.yMin / head.unitsPerEm;
1369 case BBOXURX:
1370 return fontSize * head.xMax / head.unitsPerEm;
1371 case BBOXURY:
1372 return fontSize * head.yMax / head.unitsPerEm;
1373 case AWT_ASCENT:
1374 return fontSize * hhea.Ascender / head.unitsPerEm;
1375 case AWT_DESCENT:
1376 return fontSize * hhea.Descender / head.unitsPerEm;
1377 case AWT_LEADING:
1378 return fontSize * hhea.LineGap / head.unitsPerEm;
1379 case AWT_MAXADVANCE:
1380 return fontSize * hhea.advanceWidthMax / head.unitsPerEm;
1381 case UNDERLINE_POSITION:
1382 return (underlinePosition - underlineThickness / 2) * fontSize / head.unitsPerEm;
1383 case UNDERLINE_THICKNESS:
1384 return underlineThickness * fontSize / head.unitsPerEm;
1385 case STRIKETHROUGH_POSITION:
1386 return os_2.yStrikeoutPosition * fontSize / head.unitsPerEm;
1387 case STRIKETHROUGH_THICKNESS:
1388 return os_2.yStrikeoutSize * fontSize / head.unitsPerEm;
1389 case SUBSCRIPT_SIZE:
1390 return os_2.ySubscriptYSize * fontSize / head.unitsPerEm;
1391 case SUBSCRIPT_OFFSET:
1392 return -os_2.ySubscriptYOffset * fontSize / head.unitsPerEm;
1393 case SUPERSCRIPT_SIZE:
1394 return os_2.ySuperscriptYSize * fontSize / head.unitsPerEm;
1395 case SUPERSCRIPT_OFFSET:
1396 return os_2.ySuperscriptYOffset * fontSize / head.unitsPerEm;
1397 }
1398 return 0;
1399 }
1400
1401 /** Gets the glyph index and metrics for a character.
1402 * @param c the character
1403 * @return an <CODE>int</CODE> array with {glyph index, width}
1404 */
1405 public int[] getMetricsTT(int c) {
1406 if (cmapExt != null)
1407 return (int[])cmapExt.get(new Integer(c));
1408 if (!fontSpecific && cmap31 != null)
1409 return (int[])cmap31.get(new Integer(c));
1410 if (fontSpecific && cmap10 != null)
1411 return (int[])cmap10.get(new Integer(c));
1412 if (cmap31 != null)
1413 return (int[])cmap31.get(new Integer(c));
1414 if (cmap10 != null)
1415 return (int[])cmap10.get(new Integer(c));
1416 return null;
1417 }
1418
1419 /** Gets the postscript font name.
1420 * @return the postscript font name
1421 */
1422 public String getPostscriptFontName() {
1423 return fontName;
1424 }
1425
1426 /** Gets the code pages supported by the font.
1427 * @return the code pages supported by the font
1428 */
1429 public String[] getCodePagesSupported() {
1430 long cp = (((long)os_2.ulCodePageRange2) << 32) + (os_2.ulCodePageRange1 & 0xffffffffL);
1431 int count = 0;
1432 long bit = 1;
1433 for (int k = 0; k < 64; ++k) {
1434 if ((cp & bit) != 0 && codePages[k] != null)
1435 ++count;
1436 bit <<= 1;
1437 }
1438 String ret[] = new String[count];
1439 count = 0;
1440 bit = 1;
1441 for (int k = 0; k < 64; ++k) {
1442 if ((cp & bit) != 0 && codePages[k] != null)
1443 ret[count++] = codePages[k];
1444 bit <<= 1;
1445 }
1446 return ret;
1447 }
1448
1449 /** Gets the full name of the font. If it is a True Type font
1450 * each array element will have {Platform ID, Platform Encoding ID,
1451 * Language ID, font name}. The interpretation of this values can be
1452 * found in the Open Type specification, chapter 2, in the 'name' table.<br>
1453 * For the other fonts the array has a single element with {"", "", "",
1454 * font name}.
1455 * @return the full name of the font
1456 */
1457 public String[][] getFullFontName() {
1458 return fullName;
1459 }
1460
1461 /** Gets all the entries of the Names-Table. If it is a True Type font
1462 * each array element will have {Name ID, Platform ID, Platform Encoding ID,
1463 * Language ID, font name}. The interpretation of this values can be
1464 * found in the Open Type specification, chapter 2, in the 'name' table.<br>
1465 * For the other fonts the array has a single element with {"", "", "",
1466 * font name}.
1467 * @return the full name of the font
1468 */
1469 public String[][] getAllNameEntries() {
1470 return allNameEntries;
1471 }
1472
1473 /** Gets the family name of the font. If it is a True Type font
1474 * each array element will have {Platform ID, Platform Encoding ID,
1475 * Language ID, font name}. The interpretation of this values can be
1476 * found in the Open Type specification, chapter 2, in the 'name' table.<br>
1477 * For the other fonts the array has a single element with {"", "", "",
1478 * font name}.
1479 * @return the family name of the font
1480 */
1481 public String[][] getFamilyFontName() {
1482 return familyName;
1483 }
1484
1485 /** Checks if the font has any kerning pairs.
1486 * @return <CODE>true</CODE> if the font has any kerning pairs
1487 */
1488 public boolean hasKernPairs() {
1489 return kerning.size() > 0;
1490 }
1491
1492 /**
1493 * Sets the font name that will appear in the pdf font dictionary.
1494 * Use with care as it can easily make a font unreadable if not embedded.
1495 * @param name the new font name
1496 */
1497 public void setPostscriptFontName(String name) {
1498 fontName = name;
1499 }
1500
1501 /**
1502 * Sets the kerning between two Unicode chars.
1503 * @param char1 the first char
1504 * @param char2 the second char
1505 * @param kern the kerning to apply in normalized 1000 units
1506 * @return <code>true</code> if the kerning was applied, <code>false</code> otherwise
1507 */
1508 public boolean setKerning(int char1, int char2, int kern) {
1509 int metrics[] = getMetricsTT(char1);
1510 if (metrics == null)
1511 return false;
1512 int c1 = metrics[0];
1513 metrics = getMetricsTT(char2);
1514 if (metrics == null)
1515 return false;
1516 int c2 = metrics[0];
1517 kerning.put((c1 << 16) + c2, kern);
1518 return true;
1519 }
1520
1521 protected int[] getRawCharBBox(int c, String name) {
1522 HashMap map = null;
1523 if (name == null || cmap31 == null)
1524 map = cmap10;
1525 else
1526 map = cmap31;
1527 if (map == null)
1528 return null;
1529 int metric[] = (int[])map.get(new Integer(c));
1530 if (metric == null || bboxes == null)
1531 return null;
1532 return bboxes[metric[0]];
1533 }
1534 }