diff --git a/flatlaf-extras/build.gradle.kts b/flatlaf-extras/build.gradle.kts index c8982f65..1a16a848 100644 --- a/flatlaf-extras/build.gradle.kts +++ b/flatlaf-extras/build.gradle.kts @@ -23,7 +23,7 @@ plugins { dependencies { implementation( project( ":flatlaf-core" ) ) - implementation( libs.svgSalamander ) + implementation( libs.jsvg ) } flatlafModuleInfo { 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 e6e034aa..9527eea2 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 @@ -30,6 +30,7 @@ import java.awt.image.RGBImageFilter; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -49,9 +50,9 @@ import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.MultiResolutionImageSupport; import com.formdev.flatlaf.util.SoftCache; import com.formdev.flatlaf.util.UIScale; -import com.kitfox.svg.SVGDiagram; -import com.kitfox.svg.SVGException; -import com.kitfox.svg.SVGUniverse; +import com.github.weisj.jsvg.SVGDocument; +import com.github.weisj.jsvg.geometry.size.FloatSize; +import com.github.weisj.jsvg.parser.SVGLoader; /** * An icon that loads and paints SVG. @@ -62,11 +63,9 @@ public class FlatSVGIcon extends ImageIcon implements DisabledIconProvider { - // cache that uses soft references for values, which allows freeing SVG diagrams if no longer used - private static final SoftCache svgCache = new SoftCache<>(); - - // use own SVG universe so that it can not be cleared from anywhere - private static final SVGUniverse svgUniverse = new SVGUniverse(); + // cache that uses soft references for values, which allows freeing SVG documents if no longer used + private static final SoftCache svgCache = new SoftCache<>(); + private static final SVGLoader svgLoader = new SVGLoader(); private static int streamNumber; private final String name; @@ -79,7 +78,7 @@ public class FlatSVGIcon private ColorFilter colorFilter; - private SVGDiagram diagram; + private SVGDocument document; private boolean dark; private boolean loadFailed; @@ -270,7 +269,7 @@ public class FlatSVGIcon 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 cache + // get the document here and remove it from cache update(); synchronized( FlatSVGIcon.class ) { svgCache.remove( uri ); @@ -279,7 +278,12 @@ public class FlatSVGIcon private static synchronized URI loadFromStream( InputStream in ) throws IOException { try( InputStream in2 = in ) { - return svgUniverse.loadSVG( in2, "/flatlaf-stream-" + streamNumber++ ); + SVGDocument document = svgLoader.load( in2 ); + URI dummyUri = new URI( "inputStreamSVG", "/flatlaf-stream-" + streamNumber++, null ); + svgCache.put( dummyUri, document ); + return dummyUri; + } catch( URISyntaxException e ) { + throw new IllegalStateException( e ); } } @@ -293,11 +297,13 @@ public class FlatSVGIcon public FlatSVGIcon( FlatSVGIcon icon ) { this( icon.name, icon.width, icon.height, icon.scale, icon.disabled, icon.classLoader, icon.uri ); colorFilter = icon.colorFilter; - diagram = icon.diagram; + document = icon.document; dark = icon.dark; } - protected FlatSVGIcon( String name, int width, int height, float scale, boolean disabled, ClassLoader classLoader, URI uri ) { + 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; @@ -385,7 +391,7 @@ public class FlatSVGIcon FlatSVGIcon icon = new FlatSVGIcon( name, width, height, scale, disabled, classLoader, uri ); icon.colorFilter = colorFilter; - icon.diagram = diagram; + icon.document = document; icon.dark = dark; return icon; } @@ -404,7 +410,7 @@ public class FlatSVGIcon FlatSVGIcon icon = new FlatSVGIcon( name, width, height, scale, disabled, classLoader, uri ); icon.colorFilter = colorFilter; - icon.diagram = diagram; + icon.document = document; icon.dark = dark; return icon; } @@ -423,7 +429,7 @@ public class FlatSVGIcon FlatSVGIcon icon = new FlatSVGIcon( name, width, height, scale, true, classLoader, uri ); icon.colorFilter = colorFilter; - icon.diagram = diagram; + icon.document = document; icon.dark = dark; return icon; } @@ -462,18 +468,19 @@ public class FlatSVGIcon if( loadFailed ) return; - if( dark == isDarkLaf() && diagram != null ) + if( dark == isDarkLaf() && document != null ) return; dark = isDarkLaf(); // SVGs already loaded via url or input stream can not have light/dark variants - if( uri != null && diagram != null ) + if( uri != null && document != null ) return; URI uri = this.uri; + URL url = null; if( uri == null ) { - URL url = getIconURL( name, dark ); + url = getIconURL( name, dark ); if( url == null && dark ) url = getIconURL( name, false ); @@ -486,29 +493,35 @@ public class FlatSVGIcon uri = url2uri( url ); } - diagram = loadSVG( uri ); - loadFailed = (diagram == null); + if( url == null ) { + url = uri2url( uri ); + } + + document = loadSVG( uri, url ); + loadFailed = (document == null); } - static synchronized SVGDiagram loadSVG( URI uri ) { + /* + * The uri and url parameters should always match each other in the sense that they represent the same + * location. We pass both as most places + */ + static synchronized SVGDocument loadSVG( URI uri, URL url ) { // get from our cache - SVGDiagram diagram = svgCache.get( uri ); - if( diagram != null ) - return diagram; + SVGDocument document = svgCache.get( uri ); + if( document != null ) + return document; - // load/get SVG diagram - diagram = svgUniverse.getDiagram( uri ); + // load/get SVG document + document = svgLoader.load( url ); - if( diagram == null ) { + if( document == null ) { LoggingFacade.INSTANCE.logSevere( "FlatSVGIcon: failed to load '" + uri + "'", null ); return null; } - // add to our (soft) cache and remove from SVGUniverse (hard) cache - svgCache.put( uri, diagram ); - svgUniverse.removeDocument( uri ); + svgCache.put( uri, document ); - return diagram; + return document; } private URL getIconURL( String name, boolean dark ) { @@ -528,7 +541,7 @@ public class FlatSVGIcon */ public boolean hasFound() { update(); - return diagram != null; + return document != null; } /** @@ -540,7 +553,7 @@ public class FlatSVGIcon return scaleSize( width ); update(); - return scaleSize( (diagram != null) ? Math.round( diagram.getWidth() ) : 16 ); + return scaleSize( (document != null) ? Math.round( document.size().width ) : 16 ); } /** @@ -552,7 +565,7 @@ public class FlatSVGIcon return scaleSize( height ); update(); - return scaleSize( (diagram != null) ? Math.round( diagram.getHeight() ) : 16 ); + return scaleSize( (document != null) ? Math.round( document.size().height ) : 16 ); } private int scaleSize( int size ) { @@ -597,7 +610,7 @@ public class FlatSVGIcon } private void paintSvg( Graphics2D g, int x, int y ) { - if( diagram == null ) { + if( document == null ) { paintSvgError( g, x, y ); return; } @@ -607,19 +620,18 @@ public class FlatSVGIcon UIScale.scaleGraphics( g ); if( width > 0 || height > 0 ) { - double sx = (width > 0) ? width / diagram.getWidth() : 1; - double sy = (height > 0) ? height / diagram.getHeight() : 1; + FloatSize svgSize = document.size(); + double sx = (width > 0) ? width / svgSize.width : 1; + double sy = (height > 0) ? height / svgSize.height : 1; if( sx != 1 || sy != 1 ) g.scale( sx, sy ); } if( scale != 1 ) g.scale( scale, scale ); - diagram.setIgnoringClipHeuristic( true ); - try { - diagram.render( g ); - } catch( SVGException ex ) { + document.render( null, g ); + } catch( Exception ex ) { paintSvgError( g, 0, 0 ); } } @@ -670,6 +682,14 @@ public class FlatSVGIcon } } + static URL uri2url( URI uri ) { + try { + return uri.toURL(); + } catch( MalformedURLException ex ) { + throw new IllegalArgumentException( ex ); + } + } + private static Boolean darkLaf; /** diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGUtils.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGUtils.java index 8caf5992..273dca31 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGUtils.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGUtils.java @@ -28,8 +28,8 @@ import java.util.List; import javax.swing.JWindow; import com.formdev.flatlaf.util.MultiResolutionImageSupport; import com.formdev.flatlaf.util.SystemInfo; -import com.kitfox.svg.SVGDiagram; -import com.kitfox.svg.SVGException; +import com.github.weisj.jsvg.SVGDocument; +import com.github.weisj.jsvg.geometry.size.FloatSize; /** * Utility methods for SVG. @@ -83,7 +83,7 @@ public class FlatSVGUtils * @since 2 */ public static List createWindowIconImages( URL svgUrl ) { - SVGDiagram diagram = loadSVG( svgUrl ); + SVGDocument document = loadSVG( svgUrl ); if( SystemInfo.isWindows && MultiResolutionImageSupport.isAvailable() ) { // use a multi-resolution image that creates images on demand for requested sizes @@ -102,17 +102,17 @@ public class FlatSVGUtils new Dimension( 48, 48 ), // 300% new Dimension( 64, 64 ), // 400% }, dim -> { - return svg2image( diagram, dim.width, dim.height ); + return svg2image( document, dim.width, dim.height ); } ) ); } else { return Arrays.asList( - svg2image( diagram, 16, 16 ), // 100% - svg2image( diagram, 20, 20 ), // 125% - svg2image( diagram, 24, 24 ), // 150% - svg2image( diagram, 28, 28 ), // 175% - svg2image( diagram, 32, 32 ), // 200% - svg2image( diagram, 48, 48 ), // 300% - svg2image( diagram, 64, 64 ) // 400% + svg2image( document, 16, 16 ), // 100% + svg2image( document, 20, 20 ), // 125% + svg2image( document, 24, 24 ), // 150% + svg2image( document, 28, 28 ), // 175% + svg2image( document, 32, 32 ), // 200% + svg2image( document, 48, 48 ), // 300% + svg2image( document, 64, 64 ) // 400% ); } } @@ -180,53 +180,48 @@ public class FlatSVGUtils * @since 2 */ public static BufferedImage svg2image( URL svgUrl, float scaleFactor ) { - SVGDiagram diagram = loadSVG( svgUrl ); - int width = (int) (diagram.getWidth() * scaleFactor); - int height = (int) (diagram.getHeight() * scaleFactor); - return svg2image( diagram, width, height ); + SVGDocument document = loadSVG( svgUrl ); + FloatSize size = document.size(); + int width = (int) (size.width * scaleFactor); + int height = (int) (size.height * scaleFactor); + return svg2image( document, width, height ); } /** - * Creates a buffered image and renders the given SVGDiagram into it. + * Creates a buffered image and renders the given SVGDocument into it. * - * @param diagram the SVG diagram + * @param document the SVG document * @param width the width of the image * @param height the height of the image * @return the image * @throws RuntimeException if failed to render SVG file */ - public static BufferedImage svg2image( SVGDiagram diagram, int width, int height ) { + public static BufferedImage svg2image( SVGDocument document, int width, int height ) { + BufferedImage image = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB ); + + Graphics2D g = image.createGraphics(); try { - BufferedImage image = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB ); + g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); + g.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR ); - Graphics2D g = image.createGraphics(); - try { - g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); - g.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR ); + FloatSize size = document.size(); + double sx = width / size.width; + double sy = height / size.height; + if( sx != 1 || sy != 1 ) + g.scale( sx, sy ); - double sx = width / diagram.getWidth(); - double sy = height / diagram.getHeight(); - if( sx != 1 || sy != 1 ) - g.scale( sx, sy ); - - diagram.setIgnoringClipHeuristic( true ); - - diagram.render( g ); - } finally { - g.dispose(); - } - return image; - - } catch( SVGException ex ) { - throw new RuntimeException( ex ); + document.render( null, g ); + } finally { + g.dispose(); } + return image; } private static URL getResource( String svgName ) { return FlatSVGUtils.class.getResource( svgName ); } - private static SVGDiagram loadSVG( URL url ) { - return FlatSVGIcon.loadSVG( FlatSVGIcon.url2uri( url ) ); + private static SVGDocument loadSVG( URL url ) { + return FlatSVGIcon.loadSVG( FlatSVGIcon.url2uri( url ), url ); } } diff --git a/flatlaf-extras/src/main/module-info/module-info.java b/flatlaf-extras/src/main/module-info/module-info.java index 2baec393..743dc506 100644 --- a/flatlaf-extras/src/main/module-info/module-info.java +++ b/flatlaf-extras/src/main/module-info/module-info.java @@ -20,7 +20,7 @@ module com.formdev.flatlaf.extras { requires java.desktop; requires java.prefs; - requires static com.kitfox.svg; // optional at runtime + requires static com.github.weisj.jsvg; // optional at runtime requires com.formdev.flatlaf; exports com.formdev.flatlaf.extras; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5539769d..764a7cff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,6 +25,7 @@ sigtest = "org.netbeans.tools:sigtest-maven-plugin:1.7" # flatlaf-extras svgSalamander = "com.formdev:svgSalamander:1.1.3" +jsvg = "com.github.weisj:jsvg:1.0.0" # flatlaf-jide-oss jide-oss = "com.formdev:jide-oss:3.7.12"