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;
/**