diff --git a/CHANGELOG.md b/CHANGELOG.md index c301fad7..4e3749c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,8 +50,11 @@ FlatLaf Change Log applications: `lighten()`, `darken()`, `saturate()`, `desaturate()`, `spin()`, `tint()`, `shade()` and `luma()`. - Support defining fonts in FlatLaf properties files. (issue #384) -- Extras: Added class `FlatDesktop` for easy integration into macOS screen menu - (About, Preferences and Quit) when using Java 8. +- Extras: + - Added class `FlatDesktop` for easy integration into macOS screen menu + (About, Preferences and Quit) when using Java 8. + - `FlatSVGIcon`: Support loading SVG from `URL` (for JPMS), `URI`, `File` or + `InputStream`. (issues #419 and #325) #### Fixed bugs diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGIcon.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGIcon.java index 84ac540f..59c7e8a2 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGIcon.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGIcon.java @@ -27,6 +27,10 @@ import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.awt.image.RGBImageFilter; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Collections; @@ -60,6 +64,7 @@ public class FlatSVGIcon { // use own SVG universe so that it can not be cleared from anywhere private static final SVGUniverse svgUniverse = new SVGUniverse(); + private static int streamNumber; private final String name; private final int width; @@ -67,23 +72,30 @@ public class FlatSVGIcon private final float scale; private final boolean disabled; private final ClassLoader classLoader; + private final URI uri; private ColorFilter colorFilter; private SVGDiagram diagram; private boolean dark; + private boolean loadFailed; /** * Creates an SVG icon from the given resource name. *

* The SVG attributes {@code width} and {@code height} (or {@code viewBox}) * in the tag {@code } are used as icon size. + *

+ * If using Java modules, the package containing the icon must be opened in {@code module-info.java}. + * Otherwise use {@link #FlatSVGIcon(URL)}. + *

+ * This is cheap operation because the icon is only loaded when used. * - * @param name the name of the SVG resource (a '/'-separated path) + * @param name the name of the SVG resource (a '/'-separated path; e.g. {@code "com/myapp/myicon.svg"}) * @see ClassLoader#getResource(String) */ public FlatSVGIcon( String name ) { - this( name, -1, -1, 1, false, null ); + this( name, -1, -1, 1, false, null, null ); } /** @@ -92,27 +104,37 @@ public class FlatSVGIcon *

* The SVG attributes {@code width} and {@code height} (or {@code viewBox}) * in the tag {@code } are used as icon size. + *

+ * If using Java modules, the package containing the icon must be opened in {@code module-info.java}. + * Otherwise use {@link #FlatSVGIcon(URL)}. + *

+ * This is cheap operation because the icon is only loaded when used. * - * @param name the name of the SVG resource (a '/'-separated path) + * @param name the name of the SVG resource (a '/'-separated path; e.g. {@code "com/myapp/myicon.svg"}) * @param classLoader the class loader used to load the SVG resource * @see ClassLoader#getResource(String) */ public FlatSVGIcon( String name, ClassLoader classLoader ) { - this( name, -1, -1, 1, false, classLoader ); + this( name, -1, -1, 1, false, classLoader, null ); } /** * Creates an SVG icon from the given resource name with the given width and height. *

* The icon is scaled if the given size is different to the size specified in the SVG file. + *

+ * If using Java modules, the package containing the icon must be opened in {@code module-info.java}. + * Otherwise use {@link #FlatSVGIcon(URL)}. + *

+ * This is cheap operation because the icon is only loaded when used. * - * @param name the name of the SVG resource (a '/'-separated path) + * @param name the name of the SVG resource (a '/'-separated path; e.g. {@code "com/myapp/myicon.svg"}) * @param width the width of the icon * @param height the height of the icon * @see ClassLoader#getResource(String) */ public FlatSVGIcon( String name, int width, int height ) { - this( name, width, height, 1, false, null ); + this( name, width, height, 1, false, null, null ); } /** @@ -120,15 +142,20 @@ public class FlatSVGIcon * The SVG file is loaded from the given class loader. *

* The icon is scaled if the given size is different to the size specified in the SVG file. + *

+ * If using Java modules, the package containing the icon must be opened in {@code module-info.java}. + * Otherwise use {@link #FlatSVGIcon(URL)}. + *

+ * This is cheap operation because the icon is only loaded when used. * - * @param name the name of the SVG resource (a '/'-separated path) + * @param name the name of the SVG resource (a '/'-separated path; e.g. {@code "com/myapp/myicon.svg"}) * @param width the width of the icon * @param height the height of the icon * @param classLoader the class loader used to load the SVG resource * @see ClassLoader#getResource(String) */ public FlatSVGIcon( String name, int width, int height, ClassLoader classLoader ) { - this( name, width, height, 1, false, classLoader ); + this( name, width, height, 1, false, classLoader, null ); } /** @@ -137,13 +164,18 @@ public class FlatSVGIcon * The SVG attributes {@code width} and {@code height} (or {@code viewBox}) * in the tag {@code } are used as base icon size, which is multiplied * by the given scale factor. + *

+ * If using Java modules, the package containing the icon must be opened in {@code module-info.java}. + * Otherwise use {@link #FlatSVGIcon(URL)}. + *

+ * This is cheap operation because the icon is only loaded when used. * - * @param name the name of the SVG resource (a '/'-separated path) + * @param name the name of the SVG resource (a '/'-separated path; e.g. {@code "com/myapp/myicon.svg"}) * @param scale the amount by which the icon size is scaled * @see ClassLoader#getResource(String) */ public FlatSVGIcon( String name, float scale ) { - this( name, -1, -1, scale, false, null ); + this( name, -1, -1, scale, false, null, null ); } /** @@ -153,23 +185,107 @@ public class FlatSVGIcon * The SVG attributes {@code width} and {@code height} (or {@code viewBox}) * in the tag {@code } are used as base icon size, which is multiplied * by the given scale factor. + *

+ * If using Java modules, the package containing the icon must be opened in {@code module-info.java}. + * Otherwise use {@link #FlatSVGIcon(URL)}. + *

+ * This is cheap operation because the icon is only loaded when used. * - * @param name the name of the SVG resource (a '/'-separated path) + * @param name the name of the SVG resource (a '/'-separated path; e.g. {@code "com/myapp/myicon.svg"}) * @param scale the amount by which the icon size is scaled * @param classLoader the class loader used to load the SVG resource * @see ClassLoader#getResource(String) */ public FlatSVGIcon( String name, float scale, ClassLoader classLoader ) { - this( name, -1, -1, scale, false, classLoader ); + this( name, -1, -1, scale, false, classLoader, null ); } - protected FlatSVGIcon( String name, int width, int height, float scale, boolean disabled, ClassLoader classLoader ) { + /** + * Creates an SVG icon from the given URL. + *

+ * The SVG attributes {@code width} and {@code height} (or {@code viewBox}) + * in the tag {@code } are used as icon size. + *

+ * This method is useful if using Java modules and the package containing the icon + * is not opened in {@code module-info.java}. + * E.g. {@code new FlatSVGIcon( getClass().getResource( "/com/myapp/myicon.svg" ) )}. + *

+ * This is cheap operation because the icon is only loaded when used. + * + * @param url the URL of the SVG resource + * @see ClassLoader#getResource(String) + * @since 2 + */ + public FlatSVGIcon( URL url ) { + this( null, -1, -1, 1, false, null, url2uri( url ) ); + } + + /** + * Creates an SVG icon from the given URI. + *

+ * The SVG attributes {@code width} and {@code height} (or {@code viewBox}) + * in the tag {@code } are used as icon size. + *

+ * This is cheap operation because the icon is only loaded when used. + * + * @param uri the URI of the SVG resource + * @see ClassLoader#getResource(String) + * @since 2 + */ + public FlatSVGIcon( URI uri ) { + this( null, -1, -1, 1, false, null, uri ); + } + + /** + * Creates an SVG icon from the given file. + *

+ * The SVG attributes {@code width} and {@code height} (or {@code viewBox}) + * in the tag {@code } are used as icon size. + *

+ * This is cheap operation because the icon is only loaded when used. + * + * @param file the SVG file + * @since 2 + */ + public FlatSVGIcon( File file ) { + this( null, -1, -1, 1, false, null, file.toURI() ); + } + + /** + * Creates an SVG icon from the given input stream. + *

+ * The SVG attributes {@code width} and {@code height} (or {@code viewBox}) + * in the tag {@code } are used as icon size. + *

+ * The input stream is loaded, parsed and closed immediately. + * + * @param in the input stream for reading a SVG resource + * @throws IOException if an I/O exception occurs + * @since 2 + */ + public FlatSVGIcon( InputStream in ) throws IOException { + this( null, -1, -1, 1, false, null, loadFromStream( in ) ); + + // since the input stream is already loaded and parsed, + // get diagram here and remove it from SVGUniverse cache + update(); + svgUniverse.removeDocument( uri ); + } + + private static URI loadFromStream( InputStream in ) throws IOException { + try( InputStream in2 = in ) { + return svgUniverse.loadSVG( in2, "/flatlaf-stream-" + (streamNumber++) ); + } + } + + protected FlatSVGIcon( String name, int width, int height, float scale, boolean disabled, ClassLoader classLoader, URI uri ) { this.name = name; this.width = width; this.height = height; this.scale = scale; this.disabled = disabled; this.classLoader = classLoader; + this.uri = uri; } /** @@ -248,7 +364,7 @@ public class FlatSVGIcon if( width == this.width && height == this.height ) return this; - FlatSVGIcon icon = new FlatSVGIcon( name, width, height, scale, disabled, classLoader ); + FlatSVGIcon icon = new FlatSVGIcon( name, width, height, scale, disabled, classLoader, uri ); icon.colorFilter = colorFilter; icon.diagram = diagram; icon.dark = dark; @@ -267,7 +383,7 @@ public class FlatSVGIcon if( scale == this.scale ) return this; - FlatSVGIcon icon = new FlatSVGIcon( name, width, height, scale, disabled, classLoader ); + FlatSVGIcon icon = new FlatSVGIcon( name, width, height, scale, disabled, classLoader, uri ); icon.colorFilter = colorFilter; icon.diagram = diagram; icon.dark = dark; @@ -286,7 +402,7 @@ public class FlatSVGIcon if( disabled ) return this; - FlatSVGIcon icon = new FlatSVGIcon( name, width, height, scale, true, classLoader ); + FlatSVGIcon icon = new FlatSVGIcon( name, width, height, scale, true, classLoader, uri ); icon.colorFilter = colorFilter; icon.diagram = diagram; icon.dark = dark; @@ -324,19 +440,39 @@ public class FlatSVGIcon } private void update() { + if( loadFailed ) + return; + if( dark == isDarkLaf() && diagram != null ) return; dark = isDarkLaf(); - URL url = getIconURL( name, dark ); - if( url == null & dark ) - url = getIconURL( name, false ); + + // SVGs already loaded via url or input stream can not have light/dark variants + if( uri != null && diagram != null ) + return; + + URI uri = this.uri; + if( uri == null ) { + URL url = getIconURL( name, dark ); + if( url == null & dark ) + url = getIconURL( name, false ); + + if( url == null ) { + loadFailed = true; + LoggingFacade.INSTANCE.logSevere( "FlatSVGIcon: resource '" + name + "' not found (if using Java modules, check whether icon package is opened in module-info.java)", null ); + return; + } + + uri = url2uri( url ); + } // load/get image - try { - diagram = svgUniverse.getDiagram( url.toURI() ); - } catch( URISyntaxException ex ) { - LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to load SVG icon '" + url + "'.", ex ); + diagram = svgUniverse.getDiagram( uri ); + + if( diagram == null ) { + loadFailed = true; + LoggingFacade.INSTANCE.logSevere( "FlatSVGIcon: failed to load '" + uri + "'", null ); } } @@ -487,6 +623,14 @@ public class FlatSVGIcon return MultiResolutionImageSupport.create( 0, dimensions, producer ); } + private static URI url2uri( URL url ) { + try { + return url.toURI(); + } catch( URISyntaxException ex ) { + throw new IllegalArgumentException( ex ); + } + } + private static Boolean darkLaf; /**