blob: a3b0cda3c86d3c7b017ba253f8b13b556b6d61b0 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.font;
27
28import java.lang.ref.WeakReference;
29import java.awt.FontFormatException;
30import java.io.FileNotFoundException;
31import java.io.IOException;
32import java.io.RandomAccessFile;
33import java.io.UnsupportedEncodingException;
34import java.lang.ref.WeakReference;
35import java.nio.ByteBuffer;
36import java.nio.ByteOrder;
37import java.nio.MappedByteBuffer;
38import java.nio.BufferUnderflowException;
39import java.nio.channels.ClosedChannelException;
40import java.nio.channels.FileChannel;
41import sun.java2d.Disposer;
42import java.util.HashSet;
43import java.util.HashMap;
44import java.awt.Font;
45
46/*
47 * Adobe Technical Note 5040 details the format of PFB files.
48 * the file is divided into ascii and binary sections. Each section
49 * starts with a header
50 * 0x8001 - start of binary data, is followed by 4 bytes length, then data
51 * 0x8002 - start of ascii data, is followed by 4 bytes length, then data
52 * 0x8003 - end of data segment
53 * The length is organised as LSB->MSB.
54 *
55 * Note: I experimented with using a MappedByteBuffer and
56 * there were two problems/questions.
57 * 1. If a global buffer is used rather than one allocated in the calling
58 * context, then we need to synchronize on all uses of that data, which
59 * means more code would beed to be synchronized with probable repercussions
60 * elsewhere.
61 * 2. It is not clear whether to free the buffer when the file is closed.
62 * If we have the contents in memory then why keep open files around?
63 * The mmapped buffer doesn't need it.
64 * Also regular GC is what frees the buffer. So closing the file and nulling
65 * out the reference still needs to wait for the buffer to be GC'd to
66 * reclaim the storage.
67 * If the contents of the buffer are persistent there's no need
68 * to worry about synchronization.
69 * Perhaps could use a WeakReference, and when its referent is gone, and
70 * need it can just reopen the file.
71 * Type1 fonts thus don't use up file descriptor references, but can
72 * use memory footprint in a way that's managed by the host O/S.
73 * The main "pain" may be the different model means code needs to be written
74 * without assumptions as to how this is handled by the different subclasses
75 * of FileFont.
76 */
77public class Type1Font extends FileFont {
78
79 WeakReference bufferRef = new WeakReference(null);
80
81 private String psName = null;
82
83 static private HashMap styleAbbreviationsMapping;
84 static private HashSet styleNameTokes;
85
86 static {
87 styleAbbreviationsMapping = new HashMap();
88 styleNameTokes = new HashSet();
89
90 /* These abbreviation rules are taken from Appendix 1 of Adobe Technical Note #5088 */
91 /* NB: this list is not complete - we did not include abbreviations which contain
92 several capital letters because current expansion algorithm do not support this.
93 (namely we have omited MM aka "Multiple Master", OsF aka "Oldstyle figures",
94 OS aka "Oldstyle", SC aka "Small caps" and DS aka "Display" */
95 String nm[] = {"Black", "Bold", "Book", "Demi", "Heavy", "Light",
96 "Meduium", "Nord", "Poster", "Regular", "Super", "Thin",
97 "Compressed", "Condensed", "Compact", "Extended", "Narrow",
98 "Inclined", "Italic", "Kursiv", "Oblique", "Upright", "Sloped",
99 "Semi", "Ultra", "Extra",
100 "Alternate", "Alternate", "Deutsche Fraktur", "Expert", "Inline", "Ornaments",
101 "Outline", "Roman", "Rounded", "Script", "Shaded", "Swash", "Titling", "Typewriter"};
102 String abbrv[] = {"Blk", "Bd", "Bk", "Dm", "Hv", "Lt",
103 "Md", "Nd", "Po", "Rg", "Su", "Th",
104 "Cm", "Cn", "Ct", "Ex", "Nr",
105 "Ic", "It", "Ks", "Obl", "Up", "Sl",
106 "Sm", "Ult", "X",
107 "A", "Alt", "Dfr", "Exp", "In", "Or",
108 "Ou", "Rm", "Rd", "Scr", "Sh", "Sw", "Ti", "Typ"};
109 /* This is only subset of names from nm[] because we want to distinguish things
110 like "Lucida Sans TypeWriter Bold" and "Lucida Sans Bold".
111 Names from "Design and/or special purpose" group are omitted. */
112 String styleTokens[] = {"Black", "Bold", "Book", "Demi", "Heavy", "Light",
113 "Medium", "Nord", "Poster", "Regular", "Super", "Thin",
114 "Compressed", "Condensed", "Compact", "Extended", "Narrow",
115 "Inclined", "Italic", "Kursiv", "Oblique", "Upright", "Sloped", "Slanted",
116 "Semi", "Ultra", "Extra"};
117
118 for(int i=0; i<nm.length; i++) {
119 styleAbbreviationsMapping.put(abbrv[i], nm[i]);
120 }
121 for(int i=0; i<styleTokens.length; i++) {
122 styleNameTokes.add(styleTokens[i]);
123 }
124 }
125
126
127 /**
128 * - does basic verification of the file
129 * - reads the names (full, family).
130 * - determines the style of the font.
131 * @throws FontFormatException - if the font can't be opened
132 * or fails verification, or there's no usable cmap
133 */
134 public Type1Font(String platname, Object nativeNames)
135 throws FontFormatException {
136 super(platname, nativeNames);
137 fontRank = Font2D.TYPE1_RANK;
138 checkedNatives = true;
139 verify();
140 }
141
142 private synchronized ByteBuffer getBuffer() throws FontFormatException {
143 MappedByteBuffer mapBuf = (MappedByteBuffer)bufferRef.get();
144 if (mapBuf == null) {
145 //System.out.println("open T1 " + platName);
146 try {
147 RandomAccessFile raf = (RandomAccessFile)
148 java.security.AccessController.doPrivileged(
149 new java.security.PrivilegedAction() {
150 public Object run() {
151 try {
152 return new RandomAccessFile(platName, "r");
153 } catch (FileNotFoundException ffne) {
154 }
155 return null;
156 }
157 });
158 FileChannel fc = raf.getChannel();
159 fileSize = (int)fc.size();
160 mapBuf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
161 mapBuf.position(0);
162 bufferRef = new WeakReference(mapBuf);
163 fc.close();
164 } catch (NullPointerException e) {
165 throw new FontFormatException(e.toString());
166 } catch (ClosedChannelException e) {
167 /* NIO I/O is interruptible, recurse to retry operation.
168 * Clear interrupts before recursing in case NIO didn't.
169 */
170 Thread.interrupted();
171 return getBuffer();
172 } catch (IOException e) {
173 throw new FontFormatException(e.toString());
174 }
175 }
176 return mapBuf;
177 }
178
179 protected void close() {
180 }
181
182 /* called from native code to read file into a direct byte buffer */
183 void readFile(ByteBuffer buffer) {
184 RandomAccessFile raf = null;
185 FileChannel fc;
186 try {
187 raf = (RandomAccessFile)
188 java.security.AccessController.doPrivileged(
189 new java.security.PrivilegedAction() {
190 public Object run() {
191 try {
192 return new RandomAccessFile(platName, "r");
193 } catch (FileNotFoundException fnfe) {
194 }
195 return null;
196 }
197 });
198 fc = raf.getChannel();
199 while (buffer.remaining() > 0 && fc.read(buffer) != -1) {}
200 } catch (NullPointerException npe) {
201 } catch (ClosedChannelException e) {
202 try {
203 if (raf != null) {
204 raf.close();
205 raf = null;
206 }
207 } catch (IOException ioe) {
208 }
209 /* NIO I/O is interruptible, recurse to retry operation.
210 * Clear interrupts before recursing in case NIO didn't.
211 */
212 Thread.interrupted();
213 readFile(buffer);
214 } catch (IOException e) {
215 } finally {
216 if (raf != null) {
217 try {
218 raf.close();
219 } catch (IOException e) {
220 }
221 }
222 }
223 }
224
225 public synchronized ByteBuffer readBlock(int offset, int length) {
226 ByteBuffer mappedBuf = null;
227 try {
228 mappedBuf = getBuffer();
229 if (offset > fileSize) {
230 offset = fileSize;
231 }
232 mappedBuf.position(offset);
233 return mappedBuf.slice();
234 } catch (FontFormatException e) {
235 return null;
236 }
237 }
238
239 private void verify() throws FontFormatException {
240 /* Normal usage should not call getBuffer(), as its state
241 * ie endianness, position etc, are shared. verify() can do
242 * this as its called only from within the constructor before
243 * there are other users of this object.
244 */
245 ByteBuffer bb = getBuffer();
246 if (bb.capacity() < 6) {
247 throw new FontFormatException("short file");
248 }
249 int val = bb.get(0) & 0xff;
250 if ((bb.get(0) & 0xff) == 0x80) {
251 verifyPFB(bb);
252 bb.position(6);
253 } else {
254 verifyPFA(bb);
255 bb.position(0);
256 }
257 initNames(bb);
258 if (familyName == null || fullName == null) {
259 throw new FontFormatException("Font name not found");
260 }
261 setStyle();
262 }
263
264 public int getFileSize() {
265 if (fileSize == 0) {
266 try {
267 getBuffer();
268 } catch (FontFormatException e) {
269 }
270 }
271 return fileSize;
272 }
273
274 private void verifyPFA(ByteBuffer bb) throws FontFormatException {
275 if (bb.getShort() != 0x2521) { // 0x2521 is %!
276 throw new FontFormatException("bad pfa font");
277 }
278 // remind - additional verification needed?
279 }
280
281 private void verifyPFB(ByteBuffer bb) throws FontFormatException {
282
283 int pos = 0;
284 while (true) {
285 try {
286 int segType = bb.getShort(pos) & 0xffff;
287 if (segType == 0x8001 || segType == 0x8002) {
288 bb.order(ByteOrder.LITTLE_ENDIAN);
289 int segLen = bb.getInt(pos+2);
290 bb.order(ByteOrder.BIG_ENDIAN);
291 if (segLen <= 0) {
292 throw new FontFormatException("bad segment length");
293 }
294 pos += segLen+6;
295 } else if (segType == 0x8003) {
296 return;
297 } else {
298 throw new FontFormatException("bad pfb file");
299 }
300 } catch (BufferUnderflowException bue) {
301 throw new FontFormatException(bue.toString());
302 } catch (Exception e) {
303 throw new FontFormatException(e.toString());
304 }
305 }
306 }
307
308 private static final int PSEOFTOKEN = 0;
309 private static final int PSNAMETOKEN = 1;
310 private static final int PSSTRINGTOKEN = 2;
311
312 /* Need to parse the ascii contents of the Type1 font file,
313 * looking for FullName, FamilyName and FontName.
314 * If explicit names are not found then extract them from first text line.
315 * Operating on bytes so can't use Java String utilities, which
316 * is a large part of why this is a hack.
317 *
318 * Also check for mandatory FontType and verify if it is supported.
319 */
320 private void initNames(ByteBuffer bb) throws FontFormatException {
321 boolean eof = false;
322 String fontType = null;
323 try {
324 //Parse font looking for explicit FullName, FamilyName and FontName
325 // (acording to Type1 spec they are optional)
326 while ((fullName == null || familyName == null || psName == null || fontType == null) && !eof) {
327 int tokenType = nextTokenType(bb);
328 if (tokenType == PSNAMETOKEN) {
329 int pos = bb.position();
330 if (bb.get(pos) == 'F') {
331 String s = getSimpleToken(bb);
332 if ("FullName".equals(s)) {
333 if (nextTokenType(bb)==PSSTRINGTOKEN) {
334 fullName = getString(bb);
335 }
336 } else if ("FamilyName".equals(s)) {
337 if (nextTokenType(bb)==PSSTRINGTOKEN) {
338 familyName = getString(bb);
339 }
340 } else if ("FontName".equals(s)) {
341 if (nextTokenType(bb)==PSNAMETOKEN) {
342 psName = getSimpleToken(bb);
343 }
344 } else if ("FontType".equals(s)) {
345 /* look for
346 /FontType id def
347 */
348 String token = getSimpleToken(bb);
349 if ("def".equals(getSimpleToken(bb))) {
350 fontType = token;
351 }
352 }
353 } else {
354 while (bb.get() > ' '); // skip token
355 }
356 } else if (tokenType == PSEOFTOKEN) {
357 eof = true;
358 }
359 }
360 } catch (Exception e) {
361 throw new FontFormatException(e.toString());
362 }
363
364 /* Ignore all fonts besides Type1 (e.g. Type3 fonts) */
365 if (!"1".equals(fontType)) {
366 throw new FontFormatException("Unsupported font type");
367 }
368
369 if (psName == null) { //no explicit FontName
370 // Try to extract font name from the first text line.
371 // According to Type1 spec first line consist of
372 // "%!FontType1-SpecVersion: FontName FontVersion"
373 // or
374 // "%!PS-AdobeFont-1.0: FontName version"
375 bb.position(0);
376 if (bb.getShort() != 0x2521) { //if pfb (do not start with "%!")
377 //skip segment header and "%!"
378 bb.position(8);
379 //NB: assume that first segment is ASCII one
380 // (is it possible to have valid Type1 font with first binary segment?)
381 }
382 String formatType = getSimpleToken(bb);
383 if (!formatType.startsWith("FontType1-") && !formatType.startsWith("PS-AdobeFont-")) {
384 throw new FontFormatException("Unsupported font format [" + formatType + "]");
385 }
386 psName = getSimpleToken(bb);
387 }
388
389 //if we got to the end of file then we did not find at least one of FullName or FamilyName
390 //Try to deduce missing names from present ones
391 //NB: At least psName must be already initialized by this moment
392 if (eof) {
393 //if we find fullName or familyName then use it as another name too
394 if (fullName != null) {
395 familyName = fullName2FamilyName(fullName);
396 } else if (familyName != null) {
397 fullName = familyName;
398 } else { //fallback - use postscript font name to deduce full and family names
399 fullName = psName2FullName(psName);
400 familyName = psName2FamilyName(psName);
401 }
402 }
403 }
404
405 private String fullName2FamilyName(String name) {
406 String res, token;
407 int len, start, end; //length of family name part
408
409 //FamilyName is truncated version of FullName
410 //Truncated tail must contain only style modifiers
411
412 end = name.length();
413
414 while (end > 0) {
415 start = end - 1;
416 while (start > 0 && name.charAt(start) != ' ')
417 start--;
418 //as soon as we meet first non style token truncate
419 // current tail and return
420 if (!isStyleToken(name.substring(start+1, end))) {
421 return name.substring(0, end);
422 }
423 end = start;
424 }
425
426 return name; //should not happen
427 }
428
429 private String expandAbbreviation(String abbr) {
430 if (styleAbbreviationsMapping.containsKey(abbr))
431 return (String) styleAbbreviationsMapping.get(abbr);
432 return abbr;
433 }
434
435 private boolean isStyleToken(String token) {
436 return styleNameTokes.contains(token);
437 }
438
439 private String psName2FullName(String name) {
440 String res;
441 int pos;
442
443 //According to Adobe technical note #5088 psName (aka FontName) has form
444 // <Family Name><VendorID>-<Weight><Width><Slant><Character Set>
445 //where spaces are not allowed.
446
447 //Conversion: Expand abbreviations in style portion (everything after '-'),
448 // replace '-' with space and insert missing spaces
449 pos = name.indexOf("-");
450 if (pos >= 0) {
451 res = expandName(name.substring(0, pos), false);
452 res += " " + expandName(name.substring(pos+1), true);
453 } else {
454 res = expandName(name, false);
455 }
456
457 return res;
458 }
459
460 private String psName2FamilyName(String name) {
461 String tmp = name;
462
463 //According to Adobe technical note #5088 psName (aka FontName) has form
464 // <Family Name><VendorID>-<Weight><Width><Slant><Character Set>
465 //where spaces are not allowed.
466
467 //Conversion: Truncate style portion (everything after '-')
468 // and insert missing spaces
469
470 if (tmp.indexOf("-") > 0) {
471 tmp = tmp.substring(0, tmp.indexOf("-"));
472 }
473
474 return expandName(tmp, false);
475 }
476
477 private int nextCapitalLetter(String s, int off) {
478 for (; (off >=0) && off < s.length(); off++) {
479 if (s.charAt(off) >= 'A' && s.charAt(off) <= 'Z')
480 return off;
481 }
482 return -1;
483 }
484
485 private String expandName(String s, boolean tryExpandAbbreviations) {
486 StringBuffer res = new StringBuffer(s.length() + 10);
487 int start=0, end;
488
489 while(start < s.length()) {
490 end = nextCapitalLetter(s, start + 1);
491 if (end < 0) {
492 end = s.length();
493 }
494
495 if (start != 0) {
496 res.append(" ");
497 }
498
499 if (tryExpandAbbreviations) {
500 res.append(expandAbbreviation(s.substring(start, end)));
501 } else {
502 res.append(s.substring(start, end));
503 }
504 start = end;
505 }
506
507 return res.toString();
508 }
509
510 /* skip lines beginning with "%" and leading white space on a line */
511 private byte skip(ByteBuffer bb) {
512 byte b = bb.get();
513 while (b == '%') {
514 while (true) {
515 b = bb.get();
516 if (b == '\r' || b == '\n') {
517 break;
518 }
519 }
520 }
521 while (b <= ' ') {
522 b = bb.get();
523 }
524 return b;
525 }
526
527 /*
528 * Token types:
529 * PSNAMETOKEN - /
530 * PSSTRINGTOKEN - literal text string
531 */
532 private int nextTokenType(ByteBuffer bb) {
533
534 try {
535 byte b = skip(bb);
536
537 while (true) {
538 if (b == (byte)'/') { // PS defined name follows.
539 return PSNAMETOKEN;
540 } else if (b == (byte)'(') { // PS string follows
541 return PSSTRINGTOKEN;
542 } else if ((b == (byte)'\r') || (b == (byte)'\n')) {
543 b = skip(bb);
544 } else {
545 b = bb.get();
546 }
547 }
548 } catch (BufferUnderflowException e) {
549 return PSEOFTOKEN;
550 }
551 }
552
553 /* Read simple token (sequence of non-whitespace characters)
554 starting from the current position.
555 Skip leading whitespaces (if any). */
556 private String getSimpleToken(ByteBuffer bb) {
557 while (bb.get() <= ' ');
558 int pos1 = bb.position()-1;
559 while (bb.get() > ' ');
560 int pos2 = bb.position();
561 byte[] nameBytes = new byte[pos2-pos1-1];
562 bb.position(pos1);
563 bb.get(nameBytes);
564 try {
565 return new String(nameBytes, "US-ASCII");
566 } catch (UnsupportedEncodingException e) {
567 return new String(nameBytes);
568 }
569 }
570
571 private String getString(ByteBuffer bb) {
572 int pos1 = bb.position();
573 while (bb.get() != ')');
574 int pos2 = bb.position();
575 byte[] nameBytes = new byte[pos2-pos1-1];
576 bb.position(pos1);
577 bb.get(nameBytes);
578 try {
579 return new String(nameBytes, "US-ASCII");
580 } catch (UnsupportedEncodingException e) {
581 return new String(nameBytes);
582 }
583 }
584
585
586 public String getPostscriptName() {
587 return psName;
588 }
589
590 protected synchronized FontScaler getScaler() {
591 if (scaler == null) {
592 return FontManager.getScaler(this, 0, false, fileSize);
593 }
594
595 return scaler;
596 }
597
598 CharToGlyphMapper getMapper() {
599 if (mapper == null) {
600 mapper = new Type1GlyphMapper(this);
601 }
602 return mapper;
603 }
604
605 public int getNumGlyphs() {
606 try {
607 return getScaler().getNumGlyphs();
608 } catch (FontScalerException e) {
609 scaler = FontManager.getNullScaler();
610 return getNumGlyphs();
611 }
612 }
613
614 public int getMissingGlyphCode() {
615 try {
616 return getScaler().getMissingGlyphCode();
617 } catch (FontScalerException e) {
618 scaler = FontManager.getNullScaler();
619 return getMissingGlyphCode();
620 }
621 }
622
623 public int getGlyphCode(char charCode) {
624 try {
625 return getScaler().getGlyphCode(charCode);
626 } catch (FontScalerException e) {
627 scaler = FontManager.getNullScaler();
628 return getGlyphCode(charCode);
629 }
630 }
631
632 public String toString() {
633 return "** Type1 Font: Family="+familyName+ " Name="+fullName+
634 " style="+style+" fileName="+platName;
635 }
636
637}