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