jdk/src/share/classes/sun/font/Type1Font.java
changeset 2 90ce3da70b43
child 558 14291c56e115
--- /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;
+    }
+
+}