--- old/modules/graphics/src/main/java/javafx/scene/text/Font.java 2016-05-19 14:56:15.280738920 -0700 +++ new/modules/graphics/src/main/java/javafx/scene/text/Font.java 2016-05-19 14:56:15.164738923 -0700 @@ -399,6 +399,59 @@ * @return the Font, or null if the font cannot be created. */ public static Font loadFont(String urlStr, double size) { + Font[] fonts = loadFontInternal(urlStr, size, false); + return (fonts == null) ? null : fonts[0]; + } + + /** + * Loads font resources from the specified URL. If the load is successful + * such that the location is readable, and it represents a supported + * font format then an array ofFont object will be returned. + *

+ * The use case for this method is for loading all fonts + * from a TrueType Collection (TTC). + *

+ * If a security manager is present, the application + * must have both permission to read from the specified URL location + * and the {@link javafx.util.FXPermission} "loadFont". + * If the application does not have permission to read from the specified + * URL location, then null is returned. + * If the application does not have the "loadFont" permission then this method + * will return an array of one element which is the default + * system font with the specified font size. + *

+ * Any failure such as a malformed URL being unable to locate or read + * from the resource, or if it doesn't represent a font, will result in + * a null return. It is the application's responsibility + * to check this before use. + *

+ * On a successful (non-null) return the fonts will be registered + * with the FX graphics system for creation by available constructors + * and factory methods, and the application should use it in this + * manner rather than calling this method again, which would + * repeat the overhead of downloading and installing the fonts. + *

+ * The font size parameter is a convenience so that in + * typical usage the application can directly use the returned (non-null) + * font rather than needing to create one via a constructor. Invalid sizes + * are those <=0 and will result in a default size. + *

+ * If the URL represents a local disk file, then no copying is performed + * and the font file is required to persist for the lifetime of the + * application. Updating the file in any manner will result + * in unspecified and likely undesired behaviours. + * + * @param urlStr from which to load the fonts, specified as a String. + * @param size of the returned fonts. + * @return array of Font, or null if the fonts cannot be created. + * @since 9 + */ + public static Font[] loadFonts(String urlStr, double size) { + return loadFontInternal(urlStr, size, true); + } + + private static Font[] loadFontInternal(String urlStr, double size, + boolean loadAll) { URL url = null; try { url = new URL(urlStr); // null string arg. is caught here. @@ -426,15 +479,17 @@ } catch (Exception e) { return null; } - return Toolkit.getToolkit().getFontLoader().loadFont(path, size); + return + Toolkit.getToolkit().getFontLoader().loadFont(path, size, loadAll); } - Font font = null; + Font[] fonts = null; URLConnection connection = null; InputStream in = null; try { connection = url.openConnection(); in = connection.getInputStream(); - font = Toolkit.getToolkit().getFontLoader().loadFont(in, size); + fonts = + Toolkit.getToolkit().getFontLoader().loadFont(in, size, loadAll); } catch (Exception e) { return null; } finally { @@ -445,7 +500,7 @@ } catch (Exception e) { } } - return font; + return fonts; } /** @@ -454,6 +509,50 @@ * fully read, and it represents a supported font format then a * Font object will be returned. *

+ * The use case for this method is for loading all fonts + * from a TrueType Collection (TTC). + *

+ * If a security manager is present, the application + * must have the {@link javafx.util.FXPermission} "loadFont". + * If the application does not have permission then this method + * will return the default system font with the specified font size. + *

+ * Any failure such as abbreviated input, or an unsupported font format + * will result in a null return. It is the application's + * responsibility to check this before use. + *

+ * On a successful (non-null) return the fonts will be registered + * with the FX graphics system for creation by available constructors + * and factory methods, and the application should use it in this + * manner rather than calling this method again, which would + * repeat the overhead of re-reading and installing the font. + *

+ * The font size parameter is a convenience so that in + * typical usage the application can directly use the returned (non-null) + * fonts rather than needing to create one via a constructor. Invalid sizes + * are those <=0 and will result in a default size. + *

+ * This method does not close the input stream. + * @param in stream from which to load the font. + * @param size of the returned font. + * @return array of Font, or null if the fonts cannot be created. + * @since 9 + */ + public static Font loadFont(InputStream in, double size) { + if (size <= 0) { + size = getDefaultSystemFontSize(); + } + Font[] fonts = + Toolkit.getToolkit().getFontLoader().loadFont(in, size, false); + return (fonts == null) ? null : fonts[0]; + } + + /** + * Loads font resources from the specified input stream. + * If the load is successful such that the stream can be + * fully read, and it represents a supported font format then a + * an array of Font object will be returned. + *

* If a security manager is present, the application * must have the {@link javafx.util.FXPermission} "loadFont". * If the application does not have permission then this method @@ -479,11 +578,13 @@ * @param size of the returned font. * @return the Font, or null if the font cannot be created. */ - public static Font loadFont(InputStream in, double size) { + public static Font[] loadFonts(InputStream in, double size) { if (size <= 0) { size = getDefaultSystemFontSize(); } - return Toolkit.getToolkit().getFontLoader().loadFont(in, size); + Font[] fonts = + Toolkit.getToolkit().getFontLoader().loadFont(in, size, true); + return (fonts == null) ? null : fonts; } /** --- old/modules/graphics/src/main/java/com/sun/javafx/tk/FontLoader.java 2016-05-19 14:56:15.708738908 -0700 +++ new/modules/graphics/src/main/java/com/sun/javafx/tk/FontLoader.java 2016-05-19 14:56:15.596738912 -0700 @@ -39,8 +39,8 @@ public abstract List getFontNames(String family); public abstract Font font(String family, FontWeight weight, FontPosture posture, float size); - public abstract Font loadFont(InputStream in, double size); - public abstract Font loadFont(String path, double size); + public abstract Font[] loadFont(InputStream in, double size, boolean all); + public abstract Font[] loadFont(String path, double size, boolean all); public abstract FontMetrics getFontMetrics(Font font); public abstract float getCharWidth(char ch, Font font); public abstract float getSystemFontSize(); --- old/modules/graphics/src/main/java/com/sun/javafx/font/PrismFontLoader.java 2016-05-19 14:56:16.136738897 -0700 +++ new/modules/graphics/src/main/java/com/sun/javafx/font/PrismFontLoader.java 2016-05-19 14:56:16.016738900 -0700 @@ -79,7 +79,7 @@ if (p.startsWith("/")) { p = p.substring(1); try (InputStream in = loader.getResourceAsStream(p)) { - fontFactory.loadEmbeddedFont(n, in, 0, true); + fontFactory.loadEmbeddedFont(n, in, 0, true, false); } catch (Exception e) { } } @@ -88,18 +88,35 @@ } } - @Override public Font loadFont(InputStream in, double size) { - FontFactory factory = getFontFactoryFromPipeline(); - PGFont font = factory.loadEmbeddedFont(null, in, (float)size, true); - if (font != null) return createFont(font); - return null; + private Font[] createFonts(PGFont[] fonts) { + if (fonts == null || fonts.length == 0) { + return null; + } + Font[] fxFonts = new Font[fonts.length]; + for (int i=0; i fileNameToFontResourceMap = new HashMap(); - protected abstract PrismFontFile createFontFile(String name, String filename, - int fIndex, boolean register, - boolean embedded, - boolean copy, boolean tracked) - throws Exception; + protected abstract PrismFontFile + createFontFile(String name, String filename, + int fIndex, boolean register, + boolean embedded, + boolean copy, boolean tracked) + throws Exception; public abstract GlyphLayout createGlyphLayout(); @@ -257,10 +258,12 @@ // the instances one at a time so as to have visibility into the // contents of the TTC. Onus is on caller to enumerate all the fonts. private PrismFontFile createFontResource(String filename, int index) { - return createFontResource(filename, index, true, false, false, false); + return createFontResource(null, filename, index, + true, false, false, false); } - private PrismFontFile createFontResource(String filename, int index, + private PrismFontFile createFontResource(String name, + String filename, int index, boolean register, boolean embedded, boolean copy, boolean tracked) { String key = (filename+index).toLowerCase(); @@ -270,7 +273,7 @@ } try { - fr = createFontFile(null, filename, index, register, + fr = createFontFile(name, filename, index, register, embedded, copy, tracked); if (register) { storeInMap(fr.getFullName(), fr); @@ -286,60 +289,76 @@ } private PrismFontFile createFontResource(String name, String filename) { - return createFontResource(name, filename, true, false, false, false); + PrismFontFile[] pffArr = + createFontResources(name, filename, + true, false, false, false, false); + if (pffArr == null || pffArr.length == 0) { + return null; + } else { + return pffArr[0]; + } } - private PrismFontFile createFontResource(String name, String filename, - boolean register, - boolean embedded, - boolean copy, - boolean tracked) { + private PrismFontFile[] createFontResources(String name, String filename, + boolean register, + boolean embedded, + boolean copy, + boolean tracked, + boolean loadAll) { + + PrismFontFile[] fArr = null; if (filename == null) { return null; - } else { - String lcFN = filename.toLowerCase(); - if (lcFN.endsWith(".ttc")) { - int index = 0; - PrismFontFile fr, namedFR = null; - do { - String key = (filename+index).toLowerCase(); - try { - fr = fileNameToFontResourceMap.get(key); - if (fr != null) { - if (name.equals(fr.getFullName())) { - return fr; // already mapped etc. - } else { - // Already loaded this TTC component, but - // its not the one we are looking for. - continue; - } - } else { - fr = createFontFile(name, filename, index, - register, embedded, - copy, tracked); - } - } catch (Exception e) { - if (PrismFontFactory.debugFonts) { - e.printStackTrace(); - } + } + PrismFontFile fr = createFontResource(name, filename, 0, register, + embedded, copy, tracked); + if (fr == null) { + return null; + } + int cnt = (!loadAll) ? 1 : fr.getFontCount(); + fArr = new PrismFontFile[cnt]; + fArr[0] = fr; + if (cnt == 1) { // Not a TTC, or only requesting one font. + return fArr; + } + PrismFontFile.FileRefCounter rc = null; + if (copy) { + rc = fr.createFileRefCounter(); + } + int index = 1; + do { + String key = (filename+index).toLowerCase(); + try { + fr = fileNameToFontResourceMap.get(key); + if (fr != null) { + fArr[index] = fr; + continue; + } else { + fr = createFontFile(null, filename, index, + register, embedded, + copy, tracked); + if (fr == null) { return null; } - + if (rc != null) { + fr.setAndIncFileRefCounter(rc); + } + fArr[index] = fr; String fontname = fr.getFullName(); if (register) { storeInMap(fontname, fr); fileNameToFontResourceMap.put(key, fr); } - if (index == 0 || name.equals(fontname)) { - namedFR = fr; - } - } while (++index < fr.getFontCount()); - return namedFR; - } else { - return createFontResource(filename, 0, register, - embedded, copy, tracked); + } + } catch (Exception e) { + if (PrismFontFactory.debugFonts) { + e.printStackTrace(); + } + return null; } - } + + } while (++index < cnt); + return fArr; } private String dotStyleStr(boolean bold, boolean italic) { @@ -384,10 +403,16 @@ tmpFonts = new ArrayList>(); } WeakReference ref; + /* Registered fonts are enumerable by the application and are + * expected to persist until VM shutdown. + * Other fonts - notably ones temporarily loaded in a web page via + * webview - should be eligible to be collected and have their + * temp files deleted at any time. + */ if (fr.isRegistered()) { ref = new WeakReference(fr); } else { - ref = fr.createFileDisposer(this); + ref = fr.createFileDisposer(this, fr.getFileRefCounter()); } tmpFonts.add(ref); addFileCloserHook(); @@ -1434,13 +1459,15 @@ private HashMap embeddedFonts; - public PGFont loadEmbeddedFont(String name, InputStream fontStream, - float size, boolean register) { + public PGFont[] loadEmbeddedFont(String name, InputStream fontStream, + float size, + boolean register, + boolean loadAll) { if (!hasPermission()) { - return createFont(DEFAULT_FULLNAME, size); + return new PGFont[] { createFont(DEFAULT_FULLNAME, size) } ; } if (FontFileWriter.hasTempPermission()) { - return loadEmbeddedFont0(name, fontStream, size, register); + return loadEmbeddedFont0(name, fontStream, size, register, loadAll); } // Otherwise, be extra conscious of pending temp file creation and @@ -1454,7 +1481,7 @@ // Timed out waiting for resources. return null; } - return loadEmbeddedFont0(name, fontStream, size, register); + return loadEmbeddedFont0(name, fontStream, size, register, loadAll); } catch (InterruptedException e) { // Interrupted while waiting to acquire a permit. return null; @@ -1465,9 +1492,11 @@ } } - private PGFont loadEmbeddedFont0(String name, InputStream fontStream, - float size, boolean register) { - PrismFontFile fr = null; + private PGFont[] loadEmbeddedFont0(String name, InputStream fontStream, + float size, + boolean register, + boolean loadAll) { + PrismFontFile[] fr = null; FontFileWriter fontWriter = new FontFileWriter(); try { // We use a shutdown hook to close all open tmp files @@ -1483,13 +1512,13 @@ } fontWriter.closeFile(); - fr = loadEmbeddedFont(name, tFile.getPath(), register, true, - fontWriter.isTracking()); + fr = loadEmbeddedFont1(name, tFile.getPath(), register, true, + fontWriter.isTracking(), loadAll); - if (fr != null) { + if (fr != null && fr.length > 0) { /* Delete the file downloaded if it was decoded * to another file */ - if (fr.isDecoded()) { + if (fr[0].isDecoded()) { fontWriter.deleteFile(); } } @@ -1519,9 +1548,14 @@ fontWriter.deleteFile(); } } - if (fr != null) { + if (fr != null && fr.length > 0) { if (size <= 0) size = getSystemFontSize(); - return new PrismFont(fr, fr.getFullName(), size); + int num = fr.length; + PrismFont[] pFonts = new PrismFont[num]; + for (int i=0; i 0) { if (size <= 0) size = getSystemFontSize(); - return new PrismFont(fr, fr.getFullName(), size); + int num = frArr.length; + PGFont[] pgFonts = new PGFont[num]; + for (int i=0; i(); + } boolean registerEmbedded = true; - if (embeddedFonts != null) { + for (int i=0; i(); + if (!register) { + return frArr; } /* If a font name is provided then we will also store that in the @@ -1660,24 +1711,29 @@ * probably mostly futile. */ if (name != null && !name.isEmpty()) { - embeddedFonts.put(name, fr); - storeInMap(name, fr); + embeddedFonts.put(name, frArr[0]); + storeInMap(name, frArr[0]); } - removeEmbeddedFont(fullname); - embeddedFonts.put(fullname, fr); - storeInMap(fullname, fr); - family = family + dotStyleStr(fr.isBold(), fr.isItalic()); - storeInMap(family, fr); - /* The remove call is to assist the case where we have - * previously mapped into the composite map a different style - * in this family as a partial match for the application request. - * This can occur when an application requested a bold font before - * it called Font.loadFont to register the bold font. It won't - * fix the cases that already happened, but will fix the future ones. - */ - compResourceMap.remove(family.toLowerCase()); - return fr; + for (int i=0; i createFileDisposer(PrismFontFactory factory) { - FileDisposer disposer = new FileDisposer(filename, isTracked); + WeakReference createFileDisposer(PrismFontFactory factory, + FileRefCounter rc) { + FileDisposer disposer = new FileDisposer(filename, isTracked, rc); WeakReference ref = Disposer.addRecord(this, disposer); disposer.setFactory(factory, ref); return ref; @@ -121,6 +122,15 @@ AccessController.doPrivileged( (PrivilegedAction) () -> { try { + /* Although there is likely no harm in calling + * delete on a file > once, we want to refrain + * from deleting it until the shutdown hook + * code in subclasses has had an opportunity + * to clean up native accesses on the resource. + */ + if (decFileRefCount() > 0) { + return null; + } boolean delOK = (new File(filename)).delete(); if (!delOK && PrismFontFactory.debugFonts) { System.err.println("Temp file not deleted : " @@ -153,15 +163,62 @@ return fontInstallationType > 0; } + + /* A TTC file resource is shared, so reference count and delete + * only when no longer using the file from any PrismFontFile instance + */ + static class FileRefCounter { + private int refCnt = 1; // start with 1. + + synchronized int getRefCount() { + return refCnt; + } + + synchronized int increment() { + return ++refCnt; + } + + synchronized int decrement() { + return (refCnt == 0) ? 0 : --refCnt; + } + } + + private FileRefCounter refCounter = null; + + FileRefCounter getFileRefCounter() { + return refCounter; + } + + FileRefCounter createFileRefCounter() { + refCounter = new FileRefCounter(); + return refCounter; + } + + void setAndIncFileRefCounter(FileRefCounter rc) { + this.refCounter = rc; + this.refCounter.increment(); + } + + int decFileRefCount() { + if (refCounter == null) { + return 0; + } else { + return refCounter.decrement(); + } + } + static class FileDisposer implements DisposerRecord { String fileName; boolean isTracked; + FileRefCounter refCounter; PrismFontFactory factory; WeakReference refKey; - public FileDisposer(String fileName, boolean isTracked) { + public FileDisposer(String fileName, boolean isTracked, + FileRefCounter rc) { this.fileName = fileName; this.isTracked = isTracked; + this.refCounter = rc; } public void setFactory(PrismFontFactory factory, @@ -175,6 +232,11 @@ AccessController.doPrivileged( (PrivilegedAction) () -> { try { + if (refCounter != null && + refCounter.decrement() > 0) + { + return null; + } File file = new File(fileName); int size = (int)file.length(); file.delete(); --- old/modules/graphics/src/main/java/com/sun/prism/j2d/J2DFontFactory.java 2016-05-19 14:56:18.236738839 -0700 +++ new/modules/graphics/src/main/java/com/sun/prism/j2d/J2DFontFactory.java 2016-05-19 14:56:18.116738843 -0700 @@ -95,20 +95,27 @@ * input streams on it to both prism and 2D, then when they are done, * remove it. */ - public PGFont loadEmbeddedFont(String name, InputStream fontStream, - float size, boolean register) { + public PGFont[] loadEmbeddedFont(String name, InputStream fontStream, + float size, + boolean register, + boolean loadAll) { if (!hasPermission()) { - return createFont(DEFAULT_FULLNAME, size); + PGFont[] fonts = new PGFont[1]; + fonts[0] = createFont(DEFAULT_FULLNAME, size); + return fonts; } - PGFont font = prismFontFactory.loadEmbeddedFont(name, fontStream, - size, register); - - if (font == null) return null; - final FontResource fr = font.getFontResource(); - registerFont(font.getFontResource()); - return font; + PGFont[] fonts = + prismFontFactory.loadEmbeddedFont(name, fontStream, + size, register, loadAll); + + if (fonts == null || fonts.length == 0) return null; + final FontResource fr = fonts[0].getFontResource(); + // REMIND: this needs to be upgraded to use JDK9 createFont + // which can handle a collection. + registerFont(fonts[0].getFontResource()); + return fonts; } /** @@ -139,18 +146,25 @@ }); } - public PGFont loadEmbeddedFont(String name, String path, - float size, boolean register) { + public PGFont[] loadEmbeddedFont(String name, String path, + float size, + boolean register, + boolean loadAll) { if (!hasPermission()) { - return createFont(DEFAULT_FULLNAME, size); + PGFont[] fonts = new PGFont[1]; + fonts[0] = createFont(DEFAULT_FULLNAME, size); + return fonts; } - PGFont font = prismFontFactory.loadEmbeddedFont(name, path, - size, register); - - if (font == null) return null; - final FontResource fr = font.getFontResource(); + PGFont[] fonts = + prismFontFactory.loadEmbeddedFont(name, path, + size, register, loadAll); + + if (fonts == null || fonts.length == 0) return null; + // REMIND: this needs to be upgraded to use JDK9 createFont + // which can handle a collection. + final FontResource fr = fonts[0].getFontResource(); AccessController.doPrivileged(new PrivilegedAction() { public Object run() { try { @@ -163,7 +177,7 @@ return null; } }); - return font; + return fonts; } private static boolean compositeFontMethodsInitialized = false; --- old/modules/graphics/src/test/java/test/com/sun/javafx/pgstub/StubFontLoader.java 2016-05-19 14:56:18.624738829 -0700 +++ new/modules/graphics/src/test/java/test/com/sun/javafx/pgstub/StubFontLoader.java 2016-05-19 14:56:18.504738832 -0700 @@ -167,13 +167,17 @@ } @Override - public Font loadFont(InputStream in, double size) { - return new Font("not implemented", size); + public Font[] loadFont(InputStream in, double size, boolean all) { + Font[] fonts = new Font[1]; + fonts[0] = new Font("not implemented", size); + return fonts; } @Override - public Font loadFont(String urlPath, double size) { - return new Font("not implemented", size); + public Font[] loadFont(String urlPath, double size, boolean all) { + Font[] fonts = new Font[1]; + fonts[0] = new Font("not implemented", size); + return fonts; } @Override --- old/modules/web/src/main/java/com/sun/javafx/webkit/prism/WCFontCustomPlatformDataImpl.java 2016-05-19 14:56:19.036738817 -0700 +++ new/modules/web/src/main/java/com/sun/javafx/webkit/prism/WCFontCustomPlatformDataImpl.java 2016-05-19 14:56:18.912738821 -0700 @@ -40,10 +40,12 @@ WCFontCustomPlatformDataImpl(InputStream inputStream) throws IOException { FontFactory factory = GraphicsPipeline.getPipeline().getFontFactory(); - font = factory.loadEmbeddedFont(null, inputStream, 10, false); - if (font == null) { + PGFont[] fa = factory.loadEmbeddedFont(null, inputStream, + 10, false, false); + if (fa == null) { throw new IOException("Error loading font"); } + font = fa[0]; } --- /dev/null 2016-05-19 12:05:44.881391863 -0700 +++ new/tests/manual/text/LoadFonts.java 2016-05-19 14:56:19.304738810 -0700 @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import javafx.application.*; +import javafx.stage.*; +import javafx.scene.*; +import javafx.scene.layout.*; +import javafx.scene.paint.*; +import javafx.scene.text.*; + +import java.io.*; + +public class LoadFonts extends Application { + + static String filename = null; + public static void main(String[] args) { + if (args.length > 0) { + filename = args[0]; + } else { + System.err.println("Needs a font file."); + System.err.println("usage : java LoadFonts FOO.ttc"); + } + launch(args); + } + + public void start(Stage stage) { + stage.setWidth(600); + stage.setHeight(600); + Group g = new Group(); + final Scene scene = new Scene(new Group()); + scene.setFill(Color.WHITE); + VBox box = new VBox(10); + ((Group)scene.getRoot()).getChildren().add(box); + stage.setScene(scene); + + String url = "file:" + filename; + + // Load a single font from the TTC file + Font font = Font.loadFont(url, 24.0); + System.out.println(font); + if (font != null) { + addText(box, font); + } + + // Load all fonts from the TTC file + Font[] fonts = Font.loadFonts(url, 24.0); + if (fonts != null) { + for (int i=0; i