1/*
2 * Copyright (c) 2003, 2014, Oracle and/or its affiliates. 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.font;
27
28import java.lang.ref.Reference;
29import java.awt.FontFormatException;
30import java.awt.geom.GeneralPath;
31import java.awt.geom.Point2D;
32import java.awt.geom.Rectangle2D;
33import java.io.File;
34import java.nio.ByteBuffer;
35import sun.java2d.Disposer;
36import sun.java2d.DisposerRecord;
37
38import java.io.IOException;
39import java.util.List;
40import java.security.AccessController;
41import java.security.PrivilegedActionException;
42import java.security.PrivilegedExceptionAction;
43
44public abstract class FileFont extends PhysicalFont {
45
46    protected boolean useJavaRasterizer = true;
47
48    /* I/O and file operations are always synchronized on the font
49     * object. Two threads can be accessing the font and retrieving
50     * information, and synchronized only to the extent that filesystem
51     * operations require.
52     * A limited number of files can be open at a time, to limit the
53     * absorption of file descriptors. If a file needs to be opened
54     * when there are none free, then the synchronization of all I/O
55     * ensures that any in progress operation will complete before some
56     * other thread closes the descriptor in order to allocate another one.
57     */
58    // NB consider using a RAF. FIS has finalize method so may take a
59    // little longer to be GC'd. We don't use this stream at all anyway.
60    // In fact why increase the size of a FileFont object if the stream
61    // isn't needed ..
62    //protected FileInputStream stream;
63    //protected FileChannel channel;
64    protected int fileSize;
65
66    protected FontScaler scaler;
67
68    /* The following variables are used, (and in the case of the arrays,
69     * only initialised) for select fonts where a native scaler may be
70     * used to get glyph images and metrics.
71     * glyphToCharMap is filled in on the fly and used to do a reverse
72     * lookup when a FileFont needs to get the charcode back from a glyph
73     * code so it can re-map via a NativeGlyphMapper to get a native glyph.
74     * This isn't a big hit in time, since a boolean test is sufficient
75     * to choose the usual default path, nor in memory for fonts which take
76     * the native path, since fonts have contiguous zero-based glyph indexes,
77     * and these obviously do all exist in the font.
78     */
79    protected boolean checkedNatives;
80    protected boolean useNatives;
81    protected NativeFont[] nativeFonts;
82    protected char[] glyphToCharMap;
83    /*
84     * @throws FontFormatException if the font can't be opened
85     */
86    FileFont(String platname, Object nativeNames)
87        throws FontFormatException {
88
89        super(platname, nativeNames);
90    }
91
92    FontStrike createStrike(FontStrikeDesc desc) {
93        if (!checkedNatives) {
94           checkUseNatives();
95        }
96        return new FileFontStrike(this, desc);
97    }
98
99    protected boolean checkUseNatives() {
100        checkedNatives = true;
101        return useNatives;
102    }
103
104    /* This method needs to be accessible to FontManager if there is
105     * file pool management. It may be a no-op.
106     */
107    protected abstract void close();
108
109
110    /*
111     * This is the public interface. The subclasses need to implement
112     * this. The returned block may be longer than the requested length.
113     */
114    abstract ByteBuffer readBlock(int offset, int length);
115
116    public boolean canDoStyle(int style) {
117        return true;
118    }
119
120    static void setFileToRemove(List<Font2D> fonts,
121                                File file, int cnt,
122                                CreatedFontTracker tracker)
123    {
124        CreatedFontFileDisposerRecord dr =
125            new CreatedFontFileDisposerRecord(file, cnt, tracker);
126
127        for (Font2D f : fonts) {
128            Disposer.addObjectRecord(f, dr);
129        }
130    }
131
132    /* This is called when a font scaler is determined to
133     * be unusable (ie bad).
134     * We want to replace current scaler with NullFontScaler, so
135     * we never try to use same font scaler again.
136     * Scaler native resources could have already been disposed
137     * or they will be eventually by Java2D disposer.
138     * However, it should be safe to call dispose() explicitly here.
139     *
140     * For safety we also invalidate all strike's scaler context.
141     * So, in case they cache pointer to native scaler
142     * it will not ever be used.
143     *
144     * It also appears desirable to remove all the entries from the
145     * cache so no other code will pick them up. But we can't just
146     * 'delete' them as code may be using them. And simply dropping
147     * the reference to the cache will make the reference objects
148     * unreachable and so they will not get disposed.
149     * Since a strike may hold (via java arrays) native pointers to many
150     * rasterised glyphs, this would be a memory leak.
151     * The solution is :
152     * - to move all the entries to another map where they
153     *   are no longer locatable
154     * - update FontStrikeDisposer to be able to distinguish which
155     * map they are held in via a boolean flag
156     * Since this isn't expected to be anything other than an extremely
157     * rare maybe it is not worth doing this last part.
158     */
159    synchronized void deregisterFontAndClearStrikeCache() {
160        SunFontManager fm = SunFontManager.getInstance();
161        fm.deRegisterBadFont(this);
162
163        for (Reference<FontStrike> strikeRef : strikeCache.values()) {
164            if (strikeRef != null) {
165                /* NB we know these are all FileFontStrike instances
166                 * because the cache is on this FileFont
167                 */
168                FileFontStrike strike = (FileFontStrike)strikeRef.get();
169                if (strike != null && strike.pScalerContext != 0L) {
170                    scaler.invalidateScalerContext(strike.pScalerContext);
171                }
172            }
173        }
174        if (scaler != null) {
175            scaler.dispose();
176        }
177        scaler = FontScaler.getNullScaler();
178    }
179
180    StrikeMetrics getFontMetrics(long pScalerContext) {
181        try {
182            return getScaler().getFontMetrics(pScalerContext);
183        } catch (FontScalerException fe) {
184            scaler = FontScaler.getNullScaler();
185            return getFontMetrics(pScalerContext);
186        }
187    }
188
189    float getGlyphAdvance(long pScalerContext, int glyphCode) {
190        try {
191            return getScaler().getGlyphAdvance(pScalerContext, glyphCode);
192        } catch (FontScalerException fe) {
193            scaler = FontScaler.getNullScaler();
194            return getGlyphAdvance(pScalerContext, glyphCode);
195        }
196    }
197
198    void getGlyphMetrics(long pScalerContext, int glyphCode, Point2D.Float metrics) {
199        try {
200            getScaler().getGlyphMetrics(pScalerContext, glyphCode, metrics);
201        } catch (FontScalerException fe) {
202            scaler = FontScaler.getNullScaler();
203            getGlyphMetrics(pScalerContext, glyphCode, metrics);
204        }
205    }
206
207    long getGlyphImage(long pScalerContext, int glyphCode) {
208        try {
209            return getScaler().getGlyphImage(pScalerContext, glyphCode);
210        } catch (FontScalerException fe) {
211            scaler = FontScaler.getNullScaler();
212            return getGlyphImage(pScalerContext, glyphCode);
213        }
214    }
215
216    Rectangle2D.Float getGlyphOutlineBounds(long pScalerContext, int glyphCode) {
217        try {
218            return getScaler().getGlyphOutlineBounds(pScalerContext, glyphCode);
219        } catch (FontScalerException fe) {
220            scaler = FontScaler.getNullScaler();
221            return getGlyphOutlineBounds(pScalerContext, glyphCode);
222        }
223    }
224
225    GeneralPath getGlyphOutline(long pScalerContext, int glyphCode, float x, float y) {
226        try {
227            return getScaler().getGlyphOutline(pScalerContext, glyphCode, x, y);
228        } catch (FontScalerException fe) {
229            scaler = FontScaler.getNullScaler();
230            return getGlyphOutline(pScalerContext, glyphCode, x, y);
231        }
232    }
233
234    GeneralPath getGlyphVectorOutline(long pScalerContext, int[] glyphs, int numGlyphs, float x, float y) {
235        try {
236            return getScaler().getGlyphVectorOutline(pScalerContext, glyphs, numGlyphs, x, y);
237        } catch (FontScalerException fe) {
238            scaler = FontScaler.getNullScaler();
239            return getGlyphVectorOutline(pScalerContext, glyphs, numGlyphs, x, y);
240        }
241    }
242
243    /* T1 & TT implementation differ so this method is abstract.
244       NB: null should not be returned here! */
245    protected abstract FontScaler getScaler();
246
247    protected long getUnitsPerEm() {
248        return getScaler().getUnitsPerEm();
249    }
250
251    private static class CreatedFontFileDisposerRecord
252        implements DisposerRecord {
253
254        File fontFile = null;
255        int count = 0; // number of fonts referencing this file object.
256        CreatedFontTracker tracker;
257
258        private CreatedFontFileDisposerRecord(File file, int cnt,
259                                              CreatedFontTracker tracker) {
260            fontFile = file;
261            count = (cnt > 0) ? cnt : 1;
262            this.tracker = tracker;
263        }
264
265        public void dispose() {
266            java.security.AccessController.doPrivileged(
267                 new java.security.PrivilegedAction<Object>() {
268                      public Object run() {
269                          synchronized (fontFile) {
270                              count--;
271                              if (count > 0) {
272                                  return null;
273                              }
274                          }
275                          if (fontFile != null) {
276                              try {
277                                  if (tracker != null) {
278                                      tracker.subBytes((int)fontFile.length());
279                                  }
280                                  /* REMIND: is it possible that the file is
281                                   * still open? It will be closed when the
282                                   * font2D is disposed but could this code
283                                   * execute first? If so the file would not
284                                   * be deleted on MS-windows.
285                                   */
286                                  fontFile.delete();
287                                  /* remove from delete on exit hook list : */
288                                  // FIXME: still need to be refactored
289                                  SunFontManager.getInstance().tmpFontFiles.remove(fontFile);
290                              } catch (Exception e) {
291                              }
292                          }
293                          return null;
294                      }
295            });
296        }
297    }
298
299    protected String getPublicFileName() {
300        SecurityManager sm = System.getSecurityManager();
301        if (sm == null) {
302            return platName;
303        }
304        boolean canReadProperty = true;
305
306        try {
307            sm.checkPropertyAccess("java.io.tmpdir");
308        } catch (SecurityException e) {
309            canReadProperty = false;
310        }
311
312        if (canReadProperty) {
313            return platName;
314        }
315
316        final File f = new File(platName);
317
318        Boolean isTmpFile = Boolean.FALSE;
319        try {
320            isTmpFile = AccessController.doPrivileged(
321                new PrivilegedExceptionAction<Boolean>() {
322                    public Boolean run() {
323                        File tmp = new File(System.getProperty("java.io.tmpdir"));
324                        try {
325                            String tpath = tmp.getCanonicalPath();
326                            String fpath = f.getCanonicalPath();
327
328                            return (fpath == null) || fpath.startsWith(tpath);
329                        } catch (IOException e) {
330                            return Boolean.TRUE;
331                        }
332                    }
333                }
334            );
335        } catch (PrivilegedActionException e) {
336            // unable to verify whether value of java.io.tempdir will be
337            // exposed, so return only a name of the font file.
338            isTmpFile = Boolean.TRUE;
339        }
340
341        return  isTmpFile ? "temp file" : platName;
342    }
343}
344