1 /*
   2  * Copyright (c) 2003, 2020, 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 NativeFont[] nativeFonts;
  80     protected char[] glyphToCharMap;
  81     /*
  82      * @throws FontFormatException if the font can't be opened
  83      */
  84     FileFont(String platname, Object nativeNames)
  85         throws FontFormatException {
  86 
  87         super(platname, nativeNames);
  88     }
  89 
  90     FontStrike createStrike(FontStrikeDesc desc) {
  91         return new FileFontStrike(this, desc);
  92     }
  93 
  94     /* This method needs to be accessible to FontManager if there is
  95      * file pool management. It may be a no-op.
  96      */
  97     protected abstract void close();
  98 
  99 
 100     /*
 101      * This is the public interface. The subclasses need to implement
 102      * this. The returned block may be longer than the requested length.
 103      */
 104     abstract ByteBuffer readBlock(int offset, int length);
 105 
 106     public boolean canDoStyle(int style) {
 107         return true;
 108     }
 109 
 110     static void setFileToRemove(List<Font2D> fonts,
 111                                 File file, int cnt,
 112                                 CreatedFontTracker tracker)
 113     {
 114         CreatedFontFileDisposerRecord dr =
 115             new CreatedFontFileDisposerRecord(file, cnt, tracker);
 116 
 117         for (Font2D f : fonts) {
 118             Disposer.addObjectRecord(f, dr);
 119         }
 120     }
 121 
 122     /* This is called when a font scaler is determined to
 123      * be unusable (ie bad).
 124      * We want to replace current scaler with NullFontScaler, so
 125      * we never try to use same font scaler again.
 126      * Scaler native resources could have already been disposed
 127      * or they will be eventually by Java2D disposer.
 128      * However, it should be safe to call dispose() explicitly here.
 129      *
 130      * For safety we also invalidate all strike's scaler context.
 131      * So, in case they cache pointer to native scaler
 132      * it will not ever be used.
 133      *
 134      * It also appears desirable to remove all the entries from the
 135      * cache so no other code will pick them up. But we can't just
 136      * 'delete' them as code may be using them. And simply dropping
 137      * the reference to the cache will make the reference objects
 138      * unreachable and so they will not get disposed.
 139      * Since a strike may hold (via java arrays) native pointers to many
 140      * rasterised glyphs, this would be a memory leak.
 141      * The solution is :
 142      * - to move all the entries to another map where they
 143      *   are no longer locatable
 144      * - update FontStrikeDisposer to be able to distinguish which
 145      * map they are held in via a boolean flag
 146      * Since this isn't expected to be anything other than an extremely
 147      * rare maybe it is not worth doing this last part.
 148      */
 149     synchronized void deregisterFontAndClearStrikeCache() {
 150         SunFontManager fm = SunFontManager.getInstance();
 151         fm.deRegisterBadFont(this);
 152 
 153         for (Reference<FontStrike> strikeRef : strikeCache.values()) {
 154             if (strikeRef != null) {
 155                 /* NB we know these are all FileFontStrike instances
 156                  * because the cache is on this FileFont
 157                  */
 158                 FileFontStrike strike = (FileFontStrike)strikeRef.get();
 159                 if (strike != null && strike.pScalerContext != 0L) {
 160                     scaler.invalidateScalerContext(strike.pScalerContext);
 161                 }
 162             }
 163         }
 164         if (scaler != null) {
 165             scaler.disposeScaler();
 166         }
 167         scaler = FontScaler.getNullScaler();
 168     }
 169 
 170     StrikeMetrics getFontMetrics(long pScalerContext) {
 171         try {
 172             return getScaler().getFontMetrics(pScalerContext);
 173         } catch (FontScalerException fe) {
 174             scaler = FontScaler.getNullScaler();
 175             return getFontMetrics(pScalerContext);
 176         }
 177     }
 178 
 179     float getGlyphAdvance(long pScalerContext, int glyphCode) {
 180         try {
 181             return getScaler().getGlyphAdvance(pScalerContext, glyphCode);
 182         } catch (FontScalerException fe) {
 183             scaler = FontScaler.getNullScaler();
 184             return getGlyphAdvance(pScalerContext, glyphCode);
 185         }
 186     }
 187 
 188     void getGlyphMetrics(long pScalerContext, int glyphCode, Point2D.Float metrics) {
 189         try {
 190             getScaler().getGlyphMetrics(pScalerContext, glyphCode, metrics);
 191         } catch (FontScalerException fe) {
 192             scaler = FontScaler.getNullScaler();
 193             getGlyphMetrics(pScalerContext, glyphCode, metrics);
 194         }
 195     }
 196 
 197     long getGlyphImage(long pScalerContext, int glyphCode) {
 198         try {
 199             return getScaler().getGlyphImage(pScalerContext, glyphCode);
 200         } catch (FontScalerException fe) {
 201             scaler = FontScaler.getNullScaler();
 202             return getGlyphImage(pScalerContext, glyphCode);
 203         }
 204     }
 205 
 206     Rectangle2D.Float getGlyphOutlineBounds(long pScalerContext, int glyphCode) {
 207         try {
 208             return getScaler().getGlyphOutlineBounds(pScalerContext, glyphCode);
 209         } catch (FontScalerException fe) {
 210             scaler = FontScaler.getNullScaler();
 211             return getGlyphOutlineBounds(pScalerContext, glyphCode);
 212         }
 213     }
 214 
 215     GeneralPath getGlyphOutline(long pScalerContext, int glyphCode, float x, float y) {
 216         try {
 217             return getScaler().getGlyphOutline(pScalerContext, glyphCode, x, y);
 218         } catch (FontScalerException fe) {
 219             scaler = FontScaler.getNullScaler();
 220             return getGlyphOutline(pScalerContext, glyphCode, x, y);
 221         }
 222     }
 223 
 224     GeneralPath getGlyphVectorOutline(long pScalerContext, int[] glyphs, int numGlyphs, float x, float y) {
 225         try {
 226             return getScaler().getGlyphVectorOutline(pScalerContext, glyphs, numGlyphs, x, y);
 227         } catch (FontScalerException fe) {
 228             scaler = FontScaler.getNullScaler();
 229             return getGlyphVectorOutline(pScalerContext, glyphs, numGlyphs, x, y);
 230         }
 231     }
 232 
 233     /* T1 & TT implementation differ so this method is abstract.
 234        NB: null should not be returned here! */
 235     protected abstract FontScaler getScaler();
 236 
 237     protected long getUnitsPerEm() {
 238         return getScaler().getUnitsPerEm();
 239     }
 240 
 241     private static class CreatedFontFileDisposerRecord
 242         implements DisposerRecord {
 243 
 244         File fontFile = null;
 245         int count = 0; // number of fonts referencing this file object.
 246         CreatedFontTracker tracker;
 247 
 248         private CreatedFontFileDisposerRecord(File file, int cnt,
 249                                               CreatedFontTracker tracker) {
 250             fontFile = file;
 251             count = (cnt > 0) ? cnt : 1;
 252             this.tracker = tracker;
 253         }
 254 
 255         public void dispose() {
 256             java.security.AccessController.doPrivileged(
 257                  new java.security.PrivilegedAction<Object>() {
 258                       public Object run() {
 259                           synchronized (fontFile) {
 260                               count--;
 261                               if (count > 0) {
 262                                   return null;
 263                               }
 264                           }
 265                           if (fontFile != null) {
 266                               try {
 267                                   if (tracker != null) {
 268                                       tracker.subBytes((int)fontFile.length());
 269                                   }
 270                                   /* REMIND: is it possible that the file is
 271                                    * still open? It will be closed when the
 272                                    * font2D is disposed but could this code
 273                                    * execute first? If so the file would not
 274                                    * be deleted on MS-windows.
 275                                    */
 276                                   fontFile.delete();
 277                                   /* remove from delete on exit hook list : */
 278                                   // FIXME: still need to be refactored
 279                                   SunFontManager.getInstance().tmpFontFiles.remove(fontFile);
 280                               } catch (Exception e) {
 281                               }
 282                           }
 283                           return null;
 284                       }
 285             });
 286         }
 287     }
 288 
 289     protected String getPublicFileName() {
 290         SecurityManager sm = System.getSecurityManager();
 291         if (sm == null) {
 292             return platName;
 293         }
 294         boolean canReadProperty = true;
 295 
 296         try {
 297             sm.checkPropertyAccess("java.io.tmpdir");
 298         } catch (SecurityException e) {
 299             canReadProperty = false;
 300         }
 301 
 302         if (canReadProperty) {
 303             return platName;
 304         }
 305 
 306         final File f = new File(platName);
 307 
 308         Boolean isTmpFile = Boolean.FALSE;
 309         try {
 310             isTmpFile = AccessController.doPrivileged(
 311                 new PrivilegedExceptionAction<Boolean>() {
 312                     public Boolean run() {
 313                         File tmp = new File(System.getProperty("java.io.tmpdir"));
 314                         try {
 315                             String tpath = tmp.getCanonicalPath();
 316                             String fpath = f.getCanonicalPath();
 317 
 318                             return (fpath == null) || fpath.startsWith(tpath);
 319                         } catch (IOException e) {
 320                             return Boolean.TRUE;
 321                         }
 322                     }
 323                 }
 324             );
 325         } catch (PrivilegedActionException e) {
 326             // unable to verify whether value of java.io.tempdir will be
 327             // exposed, so return only a name of the font file.
 328             isTmpFile = Boolean.TRUE;
 329         }
 330 
 331         return  isTmpFile ? "temp file" : platName;
 332     }
 333 }