--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/font/Type1Font.java Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,637 @@
+/*
+ * Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.font;
+
+import java.lang.ref.WeakReference;
+import java.awt.FontFormatException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.lang.ref.WeakReference;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.MappedByteBuffer;
+import java.nio.BufferUnderflowException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
+import sun.java2d.Disposer;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.awt.Font;
+
+/*
+ * Adobe Technical Note 5040 details the format of PFB files.
+ * the file is divided into ascii and binary sections. Each section
+ * starts with a header
+ * 0x8001 - start of binary data, is followed by 4 bytes length, then data
+ * 0x8002 - start of ascii data, is followed by 4 bytes length, then data
+ * 0x8003 - end of data segment
+ * The length is organised as LSB->MSB.
+ *
+ * Note: I experimented with using a MappedByteBuffer and
+ * there were two problems/questions.
+ * 1. If a global buffer is used rather than one allocated in the calling
+ * context, then we need to synchronize on all uses of that data, which
+ * means more code would beed to be synchronized with probable repercussions
+ * elsewhere.
+ * 2. It is not clear whether to free the buffer when the file is closed.
+ * If we have the contents in memory then why keep open files around?
+ * The mmapped buffer doesn't need it.
+ * Also regular GC is what frees the buffer. So closing the file and nulling
+ * out the reference still needs to wait for the buffer to be GC'd to
+ * reclaim the storage.
+ * If the contents of the buffer are persistent there's no need
+ * to worry about synchronization.
+ * Perhaps could use a WeakReference, and when its referent is gone, and
+ * need it can just reopen the file.
+ * Type1 fonts thus don't use up file descriptor references, but can
+ * use memory footprint in a way that's managed by the host O/S.
+ * The main "pain" may be the different model means code needs to be written
+ * without assumptions as to how this is handled by the different subclasses
+ * of FileFont.
+ */
+public class Type1Font extends FileFont {
+
+ WeakReference bufferRef = new WeakReference(null);
+
+ private String psName = null;
+
+ static private HashMap styleAbbreviationsMapping;
+ static private HashSet styleNameTokes;
+
+ static {
+ styleAbbreviationsMapping = new HashMap();
+ styleNameTokes = new HashSet();
+
+ /* These abbreviation rules are taken from Appendix 1 of Adobe Technical Note #5088 */
+ /* NB: this list is not complete - we did not include abbreviations which contain
+ several capital letters because current expansion algorithm do not support this.
+ (namely we have omited MM aka "Multiple Master", OsF aka "Oldstyle figures",
+ OS aka "Oldstyle", SC aka "Small caps" and DS aka "Display" */
+ String nm[] = {"Black", "Bold", "Book", "Demi", "Heavy", "Light",
+ "Meduium", "Nord", "Poster", "Regular", "Super", "Thin",
+ "Compressed", "Condensed", "Compact", "Extended", "Narrow",
+ "Inclined", "Italic", "Kursiv", "Oblique", "Upright", "Sloped",
+ "Semi", "Ultra", "Extra",
+ "Alternate", "Alternate", "Deutsche Fraktur", "Expert", "Inline", "Ornaments",
+ "Outline", "Roman", "Rounded", "Script", "Shaded", "Swash", "Titling", "Typewriter"};
+ String abbrv[] = {"Blk", "Bd", "Bk", "Dm", "Hv", "Lt",
+ "Md", "Nd", "Po", "Rg", "Su", "Th",
+ "Cm", "Cn", "Ct", "Ex", "Nr",
+ "Ic", "It", "Ks", "Obl", "Up", "Sl",
+ "Sm", "Ult", "X",
+ "A", "Alt", "Dfr", "Exp", "In", "Or",
+ "Ou", "Rm", "Rd", "Scr", "Sh", "Sw", "Ti", "Typ"};
+ /* This is only subset of names from nm[] because we want to distinguish things
+ like "Lucida Sans TypeWriter Bold" and "Lucida Sans Bold".
+ Names from "Design and/or special purpose" group are omitted. */
+ String styleTokens[] = {"Black", "Bold", "Book", "Demi", "Heavy", "Light",
+ "Medium", "Nord", "Poster", "Regular", "Super", "Thin",
+ "Compressed", "Condensed", "Compact", "Extended", "Narrow",
+ "Inclined", "Italic", "Kursiv", "Oblique", "Upright", "Sloped", "Slanted",
+ "Semi", "Ultra", "Extra"};
+
+ for(int i=0; i<nm.length; i++) {
+ styleAbbreviationsMapping.put(abbrv[i], nm[i]);
+ }
+ for(int i=0; i<styleTokens.length; i++) {
+ styleNameTokes.add(styleTokens[i]);
+ }
+ }
+
+
+ /**
+ * - does basic verification of the file
+ * - reads the names (full, family).
+ * - determines the style of the font.
+ * @throws FontFormatException - if the font can't be opened
+ * or fails verification, or there's no usable cmap
+ */
+ public Type1Font(String platname, Object nativeNames)
+ throws FontFormatException {
+ super(platname, nativeNames);
+ fontRank = Font2D.TYPE1_RANK;
+ checkedNatives = true;
+ verify();
+ }
+
+ private synchronized ByteBuffer getBuffer() throws FontFormatException {
+ MappedByteBuffer mapBuf = (MappedByteBuffer)bufferRef.get();
+ if (mapBuf == null) {
+ //System.out.println("open T1 " + platName);
+ try {
+ RandomAccessFile raf = (RandomAccessFile)
+ java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction() {
+ public Object run() {
+ try {
+ return new RandomAccessFile(platName, "r");
+ } catch (FileNotFoundException ffne) {
+ }
+ return null;
+ }
+ });
+ FileChannel fc = raf.getChannel();
+ fileSize = (int)fc.size();
+ mapBuf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
+ mapBuf.position(0);
+ bufferRef = new WeakReference(mapBuf);
+ fc.close();
+ } catch (NullPointerException e) {
+ throw new FontFormatException(e.toString());
+ } catch (ClosedChannelException e) {
+ /* NIO I/O is interruptible, recurse to retry operation.
+ * Clear interrupts before recursing in case NIO didn't.
+ */
+ Thread.interrupted();
+ return getBuffer();
+ } catch (IOException e) {
+ throw new FontFormatException(e.toString());
+ }
+ }
+ return mapBuf;
+ }
+
+ protected void close() {
+ }
+
+ /* called from native code to read file into a direct byte buffer */
+ void readFile(ByteBuffer buffer) {
+ RandomAccessFile raf = null;
+ FileChannel fc;
+ try {
+ raf = (RandomAccessFile)
+ java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction() {
+ public Object run() {
+ try {
+ return new RandomAccessFile(platName, "r");
+ } catch (FileNotFoundException fnfe) {
+ }
+ return null;
+ }
+ });
+ fc = raf.getChannel();
+ while (buffer.remaining() > 0 && fc.read(buffer) != -1) {}
+ } catch (NullPointerException npe) {
+ } catch (ClosedChannelException e) {
+ try {
+ if (raf != null) {
+ raf.close();
+ raf = null;
+ }
+ } catch (IOException ioe) {
+ }
+ /* NIO I/O is interruptible, recurse to retry operation.
+ * Clear interrupts before recursing in case NIO didn't.
+ */
+ Thread.interrupted();
+ readFile(buffer);
+ } catch (IOException e) {
+ } finally {
+ if (raf != null) {
+ try {
+ raf.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ public synchronized ByteBuffer readBlock(int offset, int length) {
+ ByteBuffer mappedBuf = null;
+ try {
+ mappedBuf = getBuffer();
+ if (offset > fileSize) {
+ offset = fileSize;
+ }
+ mappedBuf.position(offset);
+ return mappedBuf.slice();
+ } catch (FontFormatException e) {
+ return null;
+ }
+ }
+
+ private void verify() throws FontFormatException {
+ /* Normal usage should not call getBuffer(), as its state
+ * ie endianness, position etc, are shared. verify() can do
+ * this as its called only from within the constructor before
+ * there are other users of this object.
+ */
+ ByteBuffer bb = getBuffer();
+ if (bb.capacity() < 6) {
+ throw new FontFormatException("short file");
+ }
+ int val = bb.get(0) & 0xff;
+ if ((bb.get(0) & 0xff) == 0x80) {
+ verifyPFB(bb);
+ bb.position(6);
+ } else {
+ verifyPFA(bb);
+ bb.position(0);
+ }
+ initNames(bb);
+ if (familyName == null || fullName == null) {
+ throw new FontFormatException("Font name not found");
+ }
+ setStyle();
+ }
+
+ public int getFileSize() {
+ if (fileSize == 0) {
+ try {
+ getBuffer();
+ } catch (FontFormatException e) {
+ }
+ }
+ return fileSize;
+ }
+
+ private void verifyPFA(ByteBuffer bb) throws FontFormatException {
+ if (bb.getShort() != 0x2521) { // 0x2521 is %!
+ throw new FontFormatException("bad pfa font");
+ }
+ // remind - additional verification needed?
+ }
+
+ private void verifyPFB(ByteBuffer bb) throws FontFormatException {
+
+ int pos = 0;
+ while (true) {
+ try {
+ int segType = bb.getShort(pos) & 0xffff;
+ if (segType == 0x8001 || segType == 0x8002) {
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ int segLen = bb.getInt(pos+2);
+ bb.order(ByteOrder.BIG_ENDIAN);
+ if (segLen <= 0) {
+ throw new FontFormatException("bad segment length");
+ }
+ pos += segLen+6;
+ } else if (segType == 0x8003) {
+ return;
+ } else {
+ throw new FontFormatException("bad pfb file");
+ }
+ } catch (BufferUnderflowException bue) {
+ throw new FontFormatException(bue.toString());
+ } catch (Exception e) {
+ throw new FontFormatException(e.toString());
+ }
+ }
+ }
+
+ private static final int PSEOFTOKEN = 0;
+ private static final int PSNAMETOKEN = 1;
+ private static final int PSSTRINGTOKEN = 2;
+
+ /* Need to parse the ascii contents of the Type1 font file,
+ * looking for FullName, FamilyName and FontName.
+ * If explicit names are not found then extract them from first text line.
+ * Operating on bytes so can't use Java String utilities, which
+ * is a large part of why this is a hack.
+ *
+ * Also check for mandatory FontType and verify if it is supported.
+ */
+ private void initNames(ByteBuffer bb) throws FontFormatException {
+ boolean eof = false;
+ String fontType = null;
+ try {
+ //Parse font looking for explicit FullName, FamilyName and FontName
+ // (acording to Type1 spec they are optional)
+ while ((fullName == null || familyName == null || psName == null || fontType == null) && !eof) {
+ int tokenType = nextTokenType(bb);
+ if (tokenType == PSNAMETOKEN) {
+ int pos = bb.position();
+ if (bb.get(pos) == 'F') {
+ String s = getSimpleToken(bb);
+ if ("FullName".equals(s)) {
+ if (nextTokenType(bb)==PSSTRINGTOKEN) {
+ fullName = getString(bb);
+ }
+ } else if ("FamilyName".equals(s)) {
+ if (nextTokenType(bb)==PSSTRINGTOKEN) {
+ familyName = getString(bb);
+ }
+ } else if ("FontName".equals(s)) {
+ if (nextTokenType(bb)==PSNAMETOKEN) {
+ psName = getSimpleToken(bb);
+ }
+ } else if ("FontType".equals(s)) {
+ /* look for
+ /FontType id def
+ */
+ String token = getSimpleToken(bb);
+ if ("def".equals(getSimpleToken(bb))) {
+ fontType = token;
+ }
+ }
+ } else {
+ while (bb.get() > ' '); // skip token
+ }
+ } else if (tokenType == PSEOFTOKEN) {
+ eof = true;
+ }
+ }
+ } catch (Exception e) {
+ throw new FontFormatException(e.toString());
+ }
+
+ /* Ignore all fonts besides Type1 (e.g. Type3 fonts) */
+ if (!"1".equals(fontType)) {
+ throw new FontFormatException("Unsupported font type");
+ }
+
+ if (psName == null) { //no explicit FontName
+ // Try to extract font name from the first text line.
+ // According to Type1 spec first line consist of
+ // "%!FontType1-SpecVersion: FontName FontVersion"
+ // or
+ // "%!PS-AdobeFont-1.0: FontName version"
+ bb.position(0);
+ if (bb.getShort() != 0x2521) { //if pfb (do not start with "%!")
+ //skip segment header and "%!"
+ bb.position(8);
+ //NB: assume that first segment is ASCII one
+ // (is it possible to have valid Type1 font with first binary segment?)
+ }
+ String formatType = getSimpleToken(bb);
+ if (!formatType.startsWith("FontType1-") && !formatType.startsWith("PS-AdobeFont-")) {
+ throw new FontFormatException("Unsupported font format [" + formatType + "]");
+ }
+ psName = getSimpleToken(bb);
+ }
+
+ //if we got to the end of file then we did not find at least one of FullName or FamilyName
+ //Try to deduce missing names from present ones
+ //NB: At least psName must be already initialized by this moment
+ if (eof) {
+ //if we find fullName or familyName then use it as another name too
+ if (fullName != null) {
+ familyName = fullName2FamilyName(fullName);
+ } else if (familyName != null) {
+ fullName = familyName;
+ } else { //fallback - use postscript font name to deduce full and family names
+ fullName = psName2FullName(psName);
+ familyName = psName2FamilyName(psName);
+ }
+ }
+ }
+
+ private String fullName2FamilyName(String name) {
+ String res, token;
+ int len, start, end; //length of family name part
+
+ //FamilyName is truncated version of FullName
+ //Truncated tail must contain only style modifiers
+
+ end = name.length();
+
+ while (end > 0) {
+ start = end - 1;
+ while (start > 0 && name.charAt(start) != ' ')
+ start--;
+ //as soon as we meet first non style token truncate
+ // current tail and return
+ if (!isStyleToken(name.substring(start+1, end))) {
+ return name.substring(0, end);
+ }
+ end = start;
+ }
+
+ return name; //should not happen
+ }
+
+ private String expandAbbreviation(String abbr) {
+ if (styleAbbreviationsMapping.containsKey(abbr))
+ return (String) styleAbbreviationsMapping.get(abbr);
+ return abbr;
+ }
+
+ private boolean isStyleToken(String token) {
+ return styleNameTokes.contains(token);
+ }
+
+ private String psName2FullName(String name) {
+ String res;
+ int pos;
+
+ //According to Adobe technical note #5088 psName (aka FontName) has form
+ // <Family Name><VendorID>-<Weight><Width><Slant><Character Set>
+ //where spaces are not allowed.
+
+ //Conversion: Expand abbreviations in style portion (everything after '-'),
+ // replace '-' with space and insert missing spaces
+ pos = name.indexOf("-");
+ if (pos >= 0) {
+ res = expandName(name.substring(0, pos), false);
+ res += " " + expandName(name.substring(pos+1), true);
+ } else {
+ res = expandName(name, false);
+ }
+
+ return res;
+ }
+
+ private String psName2FamilyName(String name) {
+ String tmp = name;
+
+ //According to Adobe technical note #5088 psName (aka FontName) has form
+ // <Family Name><VendorID>-<Weight><Width><Slant><Character Set>
+ //where spaces are not allowed.
+
+ //Conversion: Truncate style portion (everything after '-')
+ // and insert missing spaces
+
+ if (tmp.indexOf("-") > 0) {
+ tmp = tmp.substring(0, tmp.indexOf("-"));
+ }
+
+ return expandName(tmp, false);
+ }
+
+ private int nextCapitalLetter(String s, int off) {
+ for (; (off >=0) && off < s.length(); off++) {
+ if (s.charAt(off) >= 'A' && s.charAt(off) <= 'Z')
+ return off;
+ }
+ return -1;
+ }
+
+ private String expandName(String s, boolean tryExpandAbbreviations) {
+ StringBuffer res = new StringBuffer(s.length() + 10);
+ int start=0, end;
+
+ while(start < s.length()) {
+ end = nextCapitalLetter(s, start + 1);
+ if (end < 0) {
+ end = s.length();
+ }
+
+ if (start != 0) {
+ res.append(" ");
+ }
+
+ if (tryExpandAbbreviations) {
+ res.append(expandAbbreviation(s.substring(start, end)));
+ } else {
+ res.append(s.substring(start, end));
+ }
+ start = end;
+ }
+
+ return res.toString();
+ }
+
+ /* skip lines beginning with "%" and leading white space on a line */
+ private byte skip(ByteBuffer bb) {
+ byte b = bb.get();
+ while (b == '%') {
+ while (true) {
+ b = bb.get();
+ if (b == '\r' || b == '\n') {
+ break;
+ }
+ }
+ }
+ while (b <= ' ') {
+ b = bb.get();
+ }
+ return b;
+ }
+
+ /*
+ * Token types:
+ * PSNAMETOKEN - /
+ * PSSTRINGTOKEN - literal text string
+ */
+ private int nextTokenType(ByteBuffer bb) {
+
+ try {
+ byte b = skip(bb);
+
+ while (true) {
+ if (b == (byte)'/') { // PS defined name follows.
+ return PSNAMETOKEN;
+ } else if (b == (byte)'(') { // PS string follows
+ return PSSTRINGTOKEN;
+ } else if ((b == (byte)'\r') || (b == (byte)'\n')) {
+ b = skip(bb);
+ } else {
+ b = bb.get();
+ }
+ }
+ } catch (BufferUnderflowException e) {
+ return PSEOFTOKEN;
+ }
+ }
+
+ /* Read simple token (sequence of non-whitespace characters)
+ starting from the current position.
+ Skip leading whitespaces (if any). */
+ private String getSimpleToken(ByteBuffer bb) {
+ while (bb.get() <= ' ');
+ int pos1 = bb.position()-1;
+ while (bb.get() > ' ');
+ int pos2 = bb.position();
+ byte[] nameBytes = new byte[pos2-pos1-1];
+ bb.position(pos1);
+ bb.get(nameBytes);
+ try {
+ return new String(nameBytes, "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ return new String(nameBytes);
+ }
+ }
+
+ private String getString(ByteBuffer bb) {
+ int pos1 = bb.position();
+ while (bb.get() != ')');
+ int pos2 = bb.position();
+ byte[] nameBytes = new byte[pos2-pos1-1];
+ bb.position(pos1);
+ bb.get(nameBytes);
+ try {
+ return new String(nameBytes, "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ return new String(nameBytes);
+ }
+ }
+
+
+ public String getPostscriptName() {
+ return psName;
+ }
+
+ protected synchronized FontScaler getScaler() {
+ if (scaler == null) {
+ return FontManager.getScaler(this, 0, false, fileSize);
+ }
+
+ return scaler;
+ }
+
+ CharToGlyphMapper getMapper() {
+ if (mapper == null) {
+ mapper = new Type1GlyphMapper(this);
+ }
+ return mapper;
+ }
+
+ public int getNumGlyphs() {
+ try {
+ return getScaler().getNumGlyphs();
+ } catch (FontScalerException e) {
+ scaler = FontManager.getNullScaler();
+ return getNumGlyphs();
+ }
+ }
+
+ public int getMissingGlyphCode() {
+ try {
+ return getScaler().getMissingGlyphCode();
+ } catch (FontScalerException e) {
+ scaler = FontManager.getNullScaler();
+ return getMissingGlyphCode();
+ }
+ }
+
+ public int getGlyphCode(char charCode) {
+ try {
+ return getScaler().getGlyphCode(charCode);
+ } catch (FontScalerException e) {
+ scaler = FontManager.getNullScaler();
+ return getGlyphCode(charCode);
+ }
+ }
+
+ public String toString() {
+ return "** Type1 Font: Family="+familyName+ " Name="+fullName+
+ " style="+style+" fileName="+platName;
+ }
+
+}