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 
26 package sun.font;
27 
28 import java.lang.ref.Reference;
29 import java.awt.FontFormatException;
30 import java.awt.geom.GeneralPath;
31 import java.awt.geom.Point2D;
32 import java.awt.geom.Rectangle2D;
33 import java.io.File;
34 import java.nio.ByteBuffer;
35 import sun.java2d.Disposer;
36 import sun.java2d.DisposerRecord;
37 
38 import java.io.IOException;
39 import java.util.List;
40 import java.security.AccessController;
41 import java.security.PrivilegedActionException;
42 import java.security.PrivilegedExceptionAction;
43 
44 public 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      */
FileFont(String platname, Object nativeNames)86     FileFont(String platname, Object nativeNames)
87         throws FontFormatException {
88 
89         super(platname, nativeNames);
90     }
91 
createStrike(FontStrikeDesc desc)92     FontStrike createStrike(FontStrikeDesc desc) {
93         if (!checkedNatives) {
94            checkUseNatives();
95         }
96         return new FileFontStrike(this, desc);
97     }
98 
checkUseNatives()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      */
close()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      */
readBlock(int offset, int length)114     abstract ByteBuffer readBlock(int offset, int length);
115 
canDoStyle(int style)116     public boolean canDoStyle(int style) {
117         return true;
118     }
119 
setFileToRemove(List<Font2D> fonts, File file, int cnt, CreatedFontTracker tracker)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      */
deregisterFontAndClearStrikeCache()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.disposeScaler();
176         }
177         scaler = FontScaler.getNullScaler();
178     }
179 
getFontMetrics(long pScalerContext)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 
getGlyphAdvance(long pScalerContext, int glyphCode)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 
getGlyphMetrics(long pScalerContext, int glyphCode, Point2D.Float metrics)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 
getGlyphImage(long pScalerContext, int glyphCode)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 
getGlyphOutlineBounds(long pScalerContext, int glyphCode)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 
getGlyphOutline(long pScalerContext, int glyphCode, float x, float y)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 
getGlyphVectorOutline(long pScalerContext, int[] glyphs, int numGlyphs, float x, float y)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! */
getScaler()245     protected abstract FontScaler getScaler();
246 
getUnitsPerEm()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 
CreatedFontFileDisposerRecord(File file, int cnt, CreatedFontTracker tracker)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 
dispose()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 
getPublicFileName()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