diff --git a/flatlaf-extras/README.md b/flatlaf-extras/README.md index 2933d1e4..55e38664 100644 --- a/flatlaf-extras/README.md +++ b/flatlaf-extras/README.md @@ -4,8 +4,7 @@ FlatLaf Extras This sub-project provides some additional components and classes: - [FlatSVGIcon](https://www.javadoc.io/doc/com.formdev/flatlaf-extras/latest/com/formdev/flatlaf/extras/FlatSVGIcon.html): - An icon that displays SVG using - [svgSalamander](https://github.com/JFormDesigner/svgSalamander).\ + An icon that displays SVG using [JSVG](https://github.com/weisJ/jsvg).\ ![FlatSVGIcon.png](../images/extras-FlatSVGIcon.png) - [FlatTriStateCheckBox](https://www.javadoc.io/doc/com.formdev/flatlaf-extras/latest/com/formdev/flatlaf/extras/components/FlatTriStateCheckBox.html): A tri-state check box.\ @@ -38,9 +37,9 @@ Otherwise download `flatlaf-extras-.jar` here: [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-extras/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-extras) -If SVG classes are used, `svgSalamander-.jar` is also required: +If SVG classes are used, `jsvg-.jar` is also required: -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/svgSalamander/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/svgSalamander) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.weisj/jsvg/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.github.weisj/jsvg) Tools 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..76117603 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,8 +30,8 @@ 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; import java.util.Collections; import java.util.HashMap; @@ -49,9 +49,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,12 +62,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(); - private static int streamNumber; + // 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 final String name; private final int width; @@ -75,11 +72,11 @@ public class FlatSVGIcon private final float scale; private final boolean disabled; private final ClassLoader classLoader; - private final URI uri; + private final URL url; private ColorFilter colorFilter; - private SVGDiagram diagram; + private SVGDocument document; private boolean dark; private boolean loadFailed; @@ -220,7 +217,7 @@ public class FlatSVGIcon * @since 2 */ public FlatSVGIcon( URL url ) { - this( null, -1, -1, 1, false, null, url2uri( url ) ); + this( null, -1, -1, 1, false, null, url ); } /** @@ -236,7 +233,7 @@ public class FlatSVGIcon * @since 2 */ public FlatSVGIcon( URI uri ) { - this( null, -1, -1, 1, false, null, uri ); + this( null, -1, -1, 1, false, null, uri2url( uri ) ); } /** @@ -251,7 +248,7 @@ public class FlatSVGIcon * @since 2 */ public FlatSVGIcon( File file ) { - this( null, -1, -1, 1, false, null, file.toURI() ); + this( null, -1, -1, 1, false, null, uri2url( file.toURI() ) ); } /** @@ -267,19 +264,15 @@ public class FlatSVGIcon * @since 2 */ public FlatSVGIcon( InputStream in ) throws IOException { - this( null, -1, -1, 1, false, null, loadFromStream( in ) ); + this( null, -1, -1, 1, false, null, null ); - // since the input stream is already loaded and parsed, - // get diagram here and remove it from cache - update(); - synchronized( FlatSVGIcon.class ) { - svgCache.remove( uri ); - } - } - - private static synchronized URI loadFromStream( InputStream in ) throws IOException { try( InputStream in2 = in ) { - return svgUniverse.loadSVG( in2, "/flatlaf-stream-" + streamNumber++ ); + document = svgLoader.load( in2 ); + + if( document == null ) { + loadFailed = true; + LoggingFacade.INSTANCE.logSevere( "FlatSVGIcon: failed to load SVG icon from input stream", null ); + } } } @@ -291,20 +284,22 @@ public class FlatSVGIcon * @since 2.0.1 */ public FlatSVGIcon( FlatSVGIcon icon ) { - this( icon.name, icon.width, icon.height, icon.scale, icon.disabled, icon.classLoader, icon.uri ); + this( icon.name, icon.width, icon.height, icon.scale, icon.disabled, icon.classLoader, icon.url ); 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, URL url ) + { this.name = name; this.width = width; this.height = height; this.scale = scale; this.disabled = disabled; this.classLoader = classLoader; - this.uri = uri; + this.url = url; } /** @@ -383,9 +378,9 @@ public class FlatSVGIcon if( width == this.width && height == this.height ) return this; - FlatSVGIcon icon = new FlatSVGIcon( name, width, height, scale, disabled, classLoader, uri ); + FlatSVGIcon icon = new FlatSVGIcon( name, width, height, scale, disabled, classLoader, url ); icon.colorFilter = colorFilter; - icon.diagram = diagram; + icon.document = document; icon.dark = dark; return icon; } @@ -402,9 +397,9 @@ public class FlatSVGIcon if( scale == this.scale ) return this; - FlatSVGIcon icon = new FlatSVGIcon( name, width, height, scale, disabled, classLoader, uri ); + FlatSVGIcon icon = new FlatSVGIcon( name, width, height, scale, disabled, classLoader, url ); icon.colorFilter = colorFilter; - icon.diagram = diagram; + icon.document = document; icon.dark = dark; return icon; } @@ -421,9 +416,9 @@ public class FlatSVGIcon if( disabled ) return this; - FlatSVGIcon icon = new FlatSVGIcon( name, width, height, scale, true, classLoader, uri ); + FlatSVGIcon icon = new FlatSVGIcon( name, width, height, scale, true, classLoader, url ); icon.colorFilter = colorFilter; - icon.diagram = diagram; + icon.document = document; icon.dark = dark; return icon; } @@ -462,18 +457,18 @@ 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 ) + // SVGs already loaded via url, file or input stream can not have light/dark variants + if( document != null && name == null ) return; - URI uri = this.uri; - if( uri == null ) { - URL url = getIconURL( name, dark ); + URL url = this.url; + if( url == null ) { + url = getIconURL( name, dark ); if( url == null && dark ) url = getIconURL( name, false ); @@ -482,33 +477,30 @@ public class FlatSVGIcon LoggingFacade.INSTANCE.logConfig( "FlatSVGIcon: resource '" + name + "' not found (if using Java modules, check whether icon package is opened in module-info.java)", null ); return; } - - uri = url2uri( url ); } - diagram = loadSVG( uri ); - loadFailed = (diagram == null); + document = loadSVG( url ); + loadFailed = (document == null); } - static synchronized SVGDiagram loadSVG( URI uri ) { + static synchronized SVGDocument loadSVG( URL url ) { // get from our cache - SVGDiagram diagram = svgCache.get( uri ); - if( diagram != null ) - return diagram; + String cacheKey = url.toString(); + SVGDocument document = svgCache.get( cacheKey ); + if( document != null ) + return document; - // load/get SVG diagram - diagram = svgUniverse.getDiagram( uri ); + // load SVG document + document = svgLoader.load( url ); - if( diagram == null ) { - LoggingFacade.INSTANCE.logSevere( "FlatSVGIcon: failed to load '" + uri + "'", null ); + if( document == null ) { + LoggingFacade.INSTANCE.logSevere( "FlatSVGIcon: failed to load '" + url + "'", null ); return null; } - // add to our (soft) cache and remove from SVGUniverse (hard) cache - svgCache.put( uri, diagram ); - svgUniverse.removeDocument( uri ); + svgCache.put( cacheKey, document ); - return diagram; + return document; } private URL getIconURL( String name, boolean dark ) { @@ -528,7 +520,7 @@ public class FlatSVGIcon */ public boolean hasFound() { update(); - return diagram != null; + return document != null; } /** @@ -540,7 +532,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 +544,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 ) { @@ -583,12 +575,7 @@ public class FlatSVGIcon Graphics2D g2 = new GraphicsFilter( (Graphics2D) g.create(), colorFilter, ColorFilter.getInstance(), grayFilter ); try { - // same hints as in FlatUIUtils.setRenderingHints() - g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); - g2.setRenderingHint( RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE ); - - // enable better image scaling - g2.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR ); + setRenderingHints( g2 ); paintSvg( g2, x, y ); } finally { @@ -597,7 +584,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 +594,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 ); } } @@ -662,10 +648,21 @@ public class FlatSVGIcon return MultiResolutionImageSupport.create( 0, dimensions, producer ); } - static URI url2uri( URL url ) { + static void setRenderingHints( Graphics2D g ) { + // enable anti-aliasing + g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); + + // disable coordinate normalization for correct line rendering + g.setRenderingHint( RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE ); + + // enable better image scaling + g.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR ); + } + + static URL uri2url( URI uri ) { try { - return url.toURI(); - } catch( URISyntaxException ex ) { + return uri.toURL(); + } catch( MalformedURLException ex ) { throw new IllegalArgumentException( ex ); } } @@ -964,6 +961,18 @@ public class FlatSVGIcon this.grayFilter = grayFilter; } + @Override + public Graphics create() { + return new GraphicsFilter( (Graphics2D) super.create(), + colorFilter, globalColorFilter, grayFilter ); + } + + @Override + public Graphics create( int x, int y, int width, int height ) { + return new GraphicsFilter( (Graphics2D) super.create( x, y, width, height ), + colorFilter, globalColorFilter, grayFilter ); + } + @Override public void setColor( Color c ) { super.setColor( filterColor( c ) ); 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..bb546fa7 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 @@ -19,7 +19,6 @@ package com.formdev.flatlaf.extras; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Image; -import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.net.URL; import java.util.Arrays; @@ -28,8 +27,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 +82,7 @@ public class FlatSVGUtils * @since 2 */ public static List createWindowIconImages( URL svgUrl ) { - SVGDiagram diagram = loadSVG( svgUrl ); + SVGDocument document = FlatSVGIcon.loadSVG( svgUrl ); if( SystemInfo.isWindows && MultiResolutionImageSupport.isAvailable() ) { // use a multi-resolution image that creates images on demand for requested sizes @@ -102,17 +101,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% ); } } @@ -148,7 +147,7 @@ public class FlatSVGUtils * @since 2 */ public static BufferedImage svg2image( URL svgUrl, int width, int height ) { - return svg2image( loadSVG( svgUrl ), width, height ); + return svg2image( FlatSVGIcon.loadSVG( svgUrl ), width, height ); } /** @@ -180,53 +179,43 @@ 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 = FlatSVGIcon.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 ) { + private 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 ); + FlatSVGIcon.setRenderingHints( g ); - 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 ) ); - } } 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 99eec0a7..65f70d4d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,7 +24,7 @@ junit = "5.7.2" 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"