From d24e3ba741418daf8ecb46b15caf7af5aef810b5 Mon Sep 17 00:00:00 2001 From: weisj <31143295+weisJ@users.noreply.github.com> Date: Wed, 29 Sep 2021 23:50:44 +0200 Subject: [PATCH] Icons: Replace svgSalamander with JSVG --- core/build.gradle.kts | 4 +- .../components/ColoredRadioButton.java | 2 +- .../core/documentation/CreateUITable.java | 77 ++--- .../github/weisj/darklaf/icon/AllIcons.java | 2 +- gradle.properties | 2 +- property-loader/build.gradle.kts | 2 +- .../properties/icons/CustomThemedIcon.java | 37 +-- .../darklaf/properties/icons/DarkSVGIcon.java | 167 ++++------- .../icons/DarkSVGIconDomProcessor.java | 48 +++ .../properties/icons/IconColorMapper.java | 278 +----------------- .../darklaf/properties/icons/IconLoader.java | 41 ++- .../properties/icons/ThemedSVGIcon.java | 40 ++- .../icons/ThemedSVGIconParserProvider.java | 158 ++++++++++ .../src/main/module/module-info.java | 2 +- .../properties/icons/SVGImageTest.java | 20 +- settings.gradle.kts | 2 +- 16 files changed, 383 insertions(+), 499 deletions(-) create mode 100644 property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/DarkSVGIconDomProcessor.java create mode 100644 property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/ThemedSVGIconParserProvider.java diff --git a/core/build.gradle.kts b/core/build.gradle.kts index e75df4ad..80c82b8d 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -19,7 +19,7 @@ dependencies { implementation(projects.darklafWindows) implementation(projects.darklafMacos) implementation(libs.swingDslLafSupport) - implementation(libs.svgSalamander) + implementation(libs.jsvg) compileOnly(libs.nullabilityAnnotations) compileOnly(libs.swingx) @@ -27,7 +27,7 @@ dependencies { compileOnly(toolLibs.autoservice.annotations) annotationProcessor(toolLibs.autoservice.processor) - testImplementation(libs.svgSalamander) + testImplementation(libs.jsvg) testImplementation(libs.swingx) testImplementation(testLibs.bundles.miglayout) testImplementation(testLibs.swingDslInspector) diff --git a/core/src/main/java/com/github/weisj/darklaf/components/ColoredRadioButton.java b/core/src/main/java/com/github/weisj/darklaf/components/ColoredRadioButton.java index e4fd38fb..c873b0e9 100644 --- a/core/src/main/java/com/github/weisj/darklaf/components/ColoredRadioButton.java +++ b/core/src/main/java/com/github/weisj/darklaf/components/ColoredRadioButton.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2020-2021 Jannis Weis + * Copyright (c) 2020-2022 Jannis Weis * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, diff --git a/core/src/test/java/com/github/weisj/darklaf/core/documentation/CreateUITable.java b/core/src/test/java/com/github/weisj/darklaf/core/documentation/CreateUITable.java index ee23e66d..dd212df2 100644 --- a/core/src/test/java/com/github/weisj/darklaf/core/documentation/CreateUITable.java +++ b/core/src/test/java/com/github/weisj/darklaf/core/documentation/CreateUITable.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2020-2021 Jannis Weis + * Copyright (c) 2020-2022 Jannis Weis * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, @@ -21,12 +21,12 @@ package com.github.weisj.darklaf.core.documentation; import java.awt.*; +import java.awt.Font; import java.awt.image.BufferedImage; import java.io.*; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -40,23 +40,12 @@ import com.github.weisj.darklaf.DarkLaf; import com.github.weisj.darklaf.LafManager; import com.github.weisj.darklaf.components.border.DropShadowBorder; import com.github.weisj.darklaf.defaults.SampleRenderer; -import com.github.weisj.darklaf.properties.icons.DarkSVGIcon; -import com.github.weisj.darklaf.properties.icons.EmptyIcon; -import com.github.weisj.darklaf.properties.icons.IconColorMapper; -import com.github.weisj.darklaf.properties.icons.StateIcon; +import com.github.weisj.darklaf.properties.icons.*; import com.github.weisj.darklaf.properties.parser.ParseResult; import com.github.weisj.darklaf.properties.parser.Parser; import com.github.weisj.darklaf.properties.parser.PrimitiveParser; import com.github.weisj.darklaf.theme.Theme; -import com.github.weisj.darklaf.util.ColorUtil; -import com.github.weisj.darklaf.util.ImageUtil; -import com.github.weisj.darklaf.util.StringUtil; -import com.github.weisj.darklaf.util.SystemInfo; -import com.github.weisj.darklaf.util.Types; -import com.kitfox.svg.LinearGradient; -import com.kitfox.svg.SVGDiagram; -import com.kitfox.svg.SVGElement; -import com.kitfox.svg.app.beans.SVGIcon; +import com.github.weisj.darklaf.util.*; public class CreateUITable { @@ -131,8 +120,8 @@ public class CreateUITable { } private UIDefaults setupThemeDefaults(final Theme theme) { + LafManager.installTheme(theme); Parser.setDebugMode(true); - LafManager.setTheme(theme); UIDefaults defaults = new DarkLaf() { @Override public Theme getTheme() { @@ -140,7 +129,6 @@ public class CreateUITable { } }.getDefaults(); Parser.setDebugMode(false); - LafManager.installTheme(theme); currentDefaults = UIManager.getLookAndFeelDefaults(); return defaults; } @@ -286,43 +274,36 @@ public class CreateUITable { } private String parseSVGIcon(final DarkSVGIcon value, final int ident) { - SVGIcon icon = value.getSVGIcon(); StringBuilder sb = new StringBuilder(StringUtil.repeat(IDENT, ident)).append("\n"); try { - readFile(icon.getSvgURI().toURL(), sb, ident + 1); - } catch (final IOException e) { - e.printStackTrace(); - } - sb.append(StringUtil.repeat(IDENT, ident)).append("\n"); - - String svg = sb.toString(); - - SVGDiagram svgDiagram = icon.getSvgUniverse().getDiagram(icon.getSvgURI()); - SVGElement defs = svgDiagram.getElement("colors"); - if (defs != null) { - List children = defs.getChildren(null); - for (Object child : children) { - if (child instanceof LinearGradient) { - float opacity = IconColorMapper.getOpacity((LinearGradient) child, currentDefaults, null); - if (opacity < 0) opacity = 1; - Color color = IconColorMapper.getColor((LinearGradient) child, currentDefaults, null); - String id = ((LinearGradient) child).getId(); - String match = "=\"url\\(#" + id + "\\)\""; - String fillReplacement = "fill=\"#" + ColorUtil.toHex(color) + "\""; - if (opacity != 1) { - fillReplacement += " fill-opacity=\"" + opacity + "\""; - svg = svg.replaceAll("fill-opacity=\"[^\"]*\"", ""); - } - svg = svg.replaceAll("fill" + match, fillReplacement); - - String strokeReplacement = "stroke=\"#" + ColorUtil.toHex(color) + "\""; - if (opacity != 1) strokeReplacement += " stroke-opacity=\"" + opacity + "\""; - svg = svg.replaceAll("stroke" + match, strokeReplacement); + readFile(value.getURI().toURL(), sb, ident + 1); + sb.append(StringUtil.repeat(IDENT, ident)).append("\n"); + + String svg = sb.toString(); + + for (Map.Entry entry : ThemedSVGIconParserProvider.getNamedColors( + new CustomThemedIcon(value, currentDefaults, CustomThemedIcon.MergeMode.REMOVE_REFERENCES)) + .entrySet()) { + String id = entry.getKey(); + String match = "=\"url\\(#" + id + "\\)\""; + String colorHex = ColorUtil.toHex(ColorUtil.removeAlpha(entry.getValue())); + String fillReplacement = "fill=\"#" + colorHex + "\""; + + float opacity = entry.getValue().getAlpha() / 255f; + if (opacity != 1) { + fillReplacement += " fill-opacity=\"" + opacity + "\""; } + svg = svg.replaceAll("fill" + match, fillReplacement); + + String strokeReplacement = "stroke=\"#" + colorHex + "\""; + if (opacity != 1) strokeReplacement += " stroke-opacity=\"" + opacity + "\""; + svg = svg.replaceAll("stroke" + match, strokeReplacement); } svg = svg.replaceAll("(\\n.*)* \\s+", ""); + return svg; + } catch (final Exception e) { + throw new RuntimeException(e); } - return svg; } private void readFile(final URL url, final StringBuilder builder, final int ident) throws IOException { diff --git a/core/src/test/java/com/github/weisj/darklaf/icon/AllIcons.java b/core/src/test/java/com/github/weisj/darklaf/icon/AllIcons.java index 895e4c09..9efd3807 100644 --- a/core/src/test/java/com/github/weisj/darklaf/icon/AllIcons.java +++ b/core/src/test/java/com/github/weisj/darklaf/icon/AllIcons.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2020-2021 Jannis Weis + * Copyright (c) 2020-2022 Jannis Weis * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, diff --git a/gradle.properties b/gradle.properties index bf059f77..4144a111 100644 --- a/gradle.properties +++ b/gradle.properties @@ -29,7 +29,7 @@ net.ltgt.errorprone.version = 2.0.2 # Dependencies # Libraries -svgSalamander.version = 1.1.2.4 +jsvg.version = 0.0.1 swingDsl.version = 0.1.3 swingx.version = 1.6.1 nullabilityAnnotations.version = 23.0.0 diff --git a/property-loader/build.gradle.kts b/property-loader/build.gradle.kts index f724f126..9e6fcfa2 100644 --- a/property-loader/build.gradle.kts +++ b/property-loader/build.gradle.kts @@ -5,7 +5,7 @@ plugins { dependencies { api(projects.darklafUtils) - implementation(libs.svgSalamander) + implementation(libs.jsvg) implementation(libs.visualPaddings) compileOnly(libs.nullabilityAnnotations) compileOnly(toolLibs.errorprone.annotations) diff --git a/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/CustomThemedIcon.java b/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/CustomThemedIcon.java index 303e94cb..e5606c5d 100644 --- a/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/CustomThemedIcon.java +++ b/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/CustomThemedIcon.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2019-2021 Jannis Weis + * Copyright (c) 2019-2022 Jannis Weis * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, @@ -22,12 +22,10 @@ package com.github.weisj.darklaf.properties.icons; import java.net.URI; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.function.Supplier; import com.github.weisj.darklaf.properties.PropertyLoader; -import com.kitfox.svg.SVGUniverse; -import com.kitfox.svg.app.beans.SVGIcon; public class CustomThemedIcon extends ThemedSVGIcon implements MutableThemedIcon { @@ -36,16 +34,6 @@ public class CustomThemedIcon extends ThemedSVGIcon implements MutableThemedIcon private Map contextDefaults; private boolean derived; - public CustomThemedIcon(final Supplier uriSupplier, final int displayWidth, final int displayHeight) { - this(uriSupplier, displayWidth, displayHeight, null); - } - - public CustomThemedIcon(final Supplier uriSupplier, final int displayWidth, final int displayHeight, - final Map colors) { - super(uriSupplier, displayWidth, displayHeight); - defaults = colors; - } - public CustomThemedIcon(final URI uri, final int displayWidth, final int displayHeight, final Map colors) { super(uri, displayWidth, displayHeight); @@ -59,9 +47,16 @@ public class CustomThemedIcon extends ThemedSVGIcon implements MutableThemedIcon public CustomThemedIcon(final DarkSVGIcon icon, final Map contextDefaults, final MergeMode mergeMode) { super(icon.getUri(), icon.getIconWidth(), icon.getIconHeight()); + List customPaints; + if (icon instanceof ThemedSVGIcon) { + icon.ensureLoaded(false); + customPaints = ((ThemedSVGIcon) icon).paints(); + } else { + ensureLoaded(false); + customPaints = paints(); + } + defaults = ThemedSVGIconParserProvider.getProperties(customPaints); setContextProperties(contextDefaults); - ensureLoaded(false); - defaults = IconColorMapper.getProperties(getSVGIcon()); mergeProperties(mergeMode, icon); } @@ -123,13 +118,6 @@ public class CustomThemedIcon extends ThemedSVGIcon implements MutableThemedIcon return new CustomThemedIcon(width, height, this); } - @Override - protected SVGIcon createSVGIcon() { - SVGIcon icon = new SVGIcon(); - icon.setSvgUniverse(new SVGUniverse()); - return icon; - } - @Override public Map getContextProperties() { return contextDefaults != LAF_CONTEXT ? contextDefaults : getContextDefaults(); @@ -143,7 +131,8 @@ public class CustomThemedIcon extends ThemedSVGIcon implements MutableThemedIcon @Override protected void patchColors() { - IconColorMapper.patchColors(getSVGIcon(), getProperties(), getContextProperties()); + super.patchColors(); + ThemedSVGIconParserProvider.patchColors(paints(), getProperties(), getContextProperties()); } public enum MergeMode { diff --git a/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/DarkSVGIcon.java b/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/DarkSVGIcon.java index c3acdf21..7604bd3d 100644 --- a/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/DarkSVGIcon.java +++ b/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/DarkSVGIcon.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2019-2021 Jannis Weis + * Copyright (c) 2019-2022 Jannis Weis * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, @@ -24,22 +24,24 @@ import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.Serializable; +import java.net.MalformedURLException; import java.net.URI; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; +import java.util.logging.Level; import java.util.logging.Logger; -import javax.swing.UIManager; +import javax.swing.*; import org.jetbrains.annotations.NotNull; import com.github.weisj.darklaf.util.LogUtil; import com.github.weisj.darklaf.util.Scale; +import com.github.weisj.jsvg.SVGDocument; +import com.github.weisj.jsvg.attributes.ViewBox; +import com.github.weisj.jsvg.geometry.size.FloatSize; +import com.github.weisj.jsvg.parser.DefaultParserProvider; +import com.github.weisj.jsvg.parser.ParserProvider; import com.github.weisj.swingdsl.visualpadding.VisualPaddingProvider; -import com.kitfox.svg.SVGException; -import com.kitfox.svg.SVGRoot; -import com.kitfox.svg.app.beans.SVGIcon; -import com.kitfox.svg.xml.StyleAttribute; /** * Icon from SVG image. @@ -58,16 +60,18 @@ public class DarkSVGIcon * accounts for diagonal lines (i.e. when the rotation is pi/4). Ideally this value would only need * to be sqrt(2) but 2.0 behaves a lot better w.r.t. floating point calculations. * - * The scale factor is only used if the icon is painted with a non trivial rotation. + * The scale factor is only used if the icon is painted with a non-trivial rotation. */ private static final double extraScale = 2.0; - private final AtomicBoolean loaded; - private final Dimension iconSize; - private final SVGIcon icon; + private final @NotNull AtomicBoolean loaded; + private final @NotNull Dimension iconSize; - private Supplier uriSupplier; - private URI uri; + private SVGDocument svgDocument; + + private Insets visualPadding; + + private final @NotNull URI uri; private IconLoader.IconKey iconKey; @@ -78,44 +82,22 @@ public class DarkSVGIcon private Image image; /** - * Method to fetch the SVG icon from a url. - * - * @param uriSupplier supplier for the uri from which to fetch the SVG icon. - * @param displayWidth display width of icon. - * @param displayHeight display height of icon. - */ - public DarkSVGIcon(final Supplier uriSupplier, final int displayWidth, final int displayHeight) { - this.uri = null; - this.uriSupplier = uriSupplier; - iconSize = new Dimension(displayWidth, displayHeight); - icon = createSVGIcon(); - icon.setAutosize(SVGIcon.AUTOSIZE_STRETCH); - icon.setAntiAlias(true); - loaded = new AtomicBoolean(false); - } - - /** - * Method to fetch the SVG icon from a url. + * Method to fetch the SVG icon from an url. * - * @param uri the uri from which to fetch the SVG icon. + * @param uri the svg uri. * @param displayWidth display width of icon. * @param displayHeight display height of icon. */ - public DarkSVGIcon(final URI uri, final int displayWidth, final int displayHeight) { + public DarkSVGIcon(final @NotNull URI uri, final int displayWidth, final int displayHeight) { this.uri = uri; - uriSupplier = null; iconSize = new Dimension(displayWidth, displayHeight); - icon = createSVGIcon(); - icon.setAutosize(SVGIcon.AUTOSIZE_STRETCH); - icon.setAntiAlias(true); loaded = new AtomicBoolean(false); } protected DarkSVGIcon(final int width, final int height, final DarkSVGIcon parent) { this.iconSize = new Dimension(width, height); - this.icon = parent.icon; + this.svgDocument = parent.svgDocument; this.uri = parent.uri; - this.uriSupplier = parent.uriSupplier; this.loaded = parent.loaded; } @@ -156,34 +138,31 @@ public class DarkSVGIcon return ensureSVGLoaded(); } - protected URI getUri() { - ensureURILoaded(); - return uri; - } - private boolean ensureSVGLoaded() { if (!isSVGLoaded()) { URI iconUri = getUri(); LOGGER.finer(() -> "Loading icon '" + iconUri.toASCIIString() + "'."); - icon.setSvgURI(iconUri); + try { + svgDocument = IconLoader.svgLoader().load(uri.toURL(), createParserProvider()); + } catch (MalformedURLException e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + } loaded.set(true); return true; } return false; } - private boolean isSVGLoaded() { - return loaded.get(); + protected @NotNull ParserProvider createParserProvider() { + return new DefaultParserProvider(); } - private void ensureURILoaded() { - if (uri == null && uriSupplier != null) { - uri = uriSupplier.get(); - uriSupplier = null; - } - if (uri == null) { - throw new IllegalStateException("Uri is null."); - } + protected @NotNull URI getUri() { + return uri; + } + + private boolean isSVGLoaded() { + return loaded.get(); } protected void updateCache(final boolean update, final Component c) { @@ -203,7 +182,6 @@ public class DarkSVGIcon @Override public Image createImage(final Dimension size) { ensureLoaded(false); - icon.setPreferredSize(size); try { BufferedImage bi = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = (Graphics2D) bi.getGraphics(); @@ -213,20 +191,10 @@ public class DarkSVGIcon RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); Object aaHint = UIManager.get(RenderingHints.KEY_TEXT_ANTIALIASING); if (aaHint != null) g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, aaHint); - icon.paintIcon(null, g, 0, 0); + svgDocument.render(null, g, new ViewBox(0, 0, size.width, size.height)); g.dispose(); return bi; } catch (final RuntimeException e) { - if (!(this instanceof ThemedSVGIcon)) { - IconColorMapper.patchColors(icon); - Image img = icon.getImage(); - /* - * If we get to here the issue was that the icon hasn't been patched because it isn't loaded as a - * themed svg icon. - */ - LOGGER.severe("Icon '" + getName(uri) + "' that defines custom colors isn't loaded as themed icon."); - return img; - } throw new RuntimeException("Exception while painting '" + uri.toASCIIString() + "'.", e); } } @@ -252,16 +220,11 @@ public class DarkSVGIcon || Scale.equalWithError(r, 3 * Math.PI / 2); } - protected SVGIcon createSVGIcon() { - return new SVGIcon(); - } - @Override public void paintIcon(final Component c, final Graphics g, final int x, final int y, final double rotation) { boolean dr = isDirectRenderingMode(); if (dr) { ensureLoaded(true); - icon.setPreferredSize(getSize()); } else { ensureImageLoaded(c, rotation); } @@ -275,17 +238,19 @@ public class DarkSVGIcon double imageHeight = dr ? size.height : image.getHeight(null); double sx = size.width / imageWidth; double sy = size.height / imageHeight; - g2.scale(sx, sy); + if (!dr) g2.scale(sx, sy); if (rotation != 0) { g2.rotate(rotation, imageWidth / 2.0, imageHeight / 2.0); } if (dr) { - getSVGIcon().paintIcon(c, g, 0, 0); + SVGDocument svg = getSVGDocument(); + svg.render((JComponent) c, (Graphics2D) g, new ViewBox(0, 0, size.width, size.height)); } else { g2.drawImage(image, 0, 0, c); + g2.scale(1 / sx, 1 / sy); } - g2.scale(1 / sx, 1 / sy); + g2.translate(-x, -y); g2.setTransform(transform); } @@ -316,12 +281,10 @@ public class DarkSVGIcon private void ensureSizeLoaded() { if (iconSize.width < 0 || iconSize.height < 0) { - SVGIcon svg = getSVGIcon(); - int autoSizeMode = svg.getAutosize(); - svg.setAutosize(SVGIcon.AUTOSIZE_NONE); - int width = svg.getIconWidthIgnoreAutosize(); - int height = svg.getIconHeightIgnoreAutosize(); - svg.setAutosize(autoSizeMode); + SVGDocument svg = getSVGDocument(); + FloatSize svgSize = svg.size(); + int width = (int) (svgSize.width + 0.5); + int height = (int) (svgSize.height + 0.5); if (iconSize.height < 0 && iconSize.width >= 0) { height = (int) ((iconSize.width * height) / (double) width); @@ -330,7 +293,7 @@ public class DarkSVGIcon width = (int) ((iconSize.height * width) / (double) height); height = iconSize.height; } else if (iconSize.width == iconSize.height && iconSize.height < -1) { - // Scale to make largest side fit the given size. + // Scale to make the largest side fit the given size. int size = Math.abs(iconSize.width); if (width == height) { width = size; @@ -348,9 +311,23 @@ public class DarkSVGIcon } } - public SVGIcon getSVGIcon() { + public SVGDocument getSVGDocument() { ensureSVGLoaded(); - return icon; + return svgDocument; + } + + @Override + public @NotNull Insets getVisualPaddings(@NotNull Component component) { + ensureSVGLoaded(); + return visualPadding != null ? visualPadding : new Insets(0, 0, 0, 0); + } + + void setVisualPadding(final Insets visualPadding) { + this.visualPadding = visualPadding; + } + + public URI getURI() { + return uri; } @Override @@ -358,8 +335,8 @@ public class DarkSVGIcon return "DarkSVGIcon{" + "loaded=" + loaded + ", iconSize=" + iconSize + - ", icon=" + icon + - ", uriSupplier=" + uriSupplier + + ", svgDocument=" + svgDocument + + ", visualPadding=" + visualPadding + ", uri=" + uri + ", iconKey=" + iconKey + ", directRendering=" + directRendering + @@ -369,22 +346,4 @@ public class DarkSVGIcon ", image=" + image + '}'; } - - @Override - @SuppressWarnings("EmptyCatch") - public @NotNull Insets getVisualPaddings(@NotNull Component component) { - SVGIcon icon = getSVGIcon(); - SVGRoot root = icon.getSvgUniverse().getDiagram(icon.getSvgURI()).getRoot(); - StyleAttribute attr = new StyleAttribute("visualPadding"); - try { - if (root.getStyle(attr, false)) { - int[] paddings = attr.getIntList(); - if (paddings.length == 4) { - return new Insets(paddings[0], paddings[1], paddings[2], paddings[3]); - } - } - } catch (SVGException ignore) { - } - return new Insets(0, 0, 0, 0); - } } diff --git a/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/DarkSVGIconDomProcessor.java b/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/DarkSVGIconDomProcessor.java new file mode 100644 index 00000000..3091bd46 --- /dev/null +++ b/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/DarkSVGIconDomProcessor.java @@ -0,0 +1,48 @@ +/* + * MIT License + * + * Copyright (c) 2021-2022 Jannis Weis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.github.weisj.darklaf.properties.icons; + +import java.awt.*; + +import org.jetbrains.annotations.NotNull; + +import com.github.weisj.jsvg.parser.DomProcessor; +import com.github.weisj.jsvg.parser.ParsedElement; + +public class DarkSVGIconDomProcessor implements DomProcessor { + protected final @NotNull T icon; + + public DarkSVGIconDomProcessor(@NotNull T icon) { + this.icon = icon; + } + + @Override + public void process(@NotNull ParsedElement root) { + float[] visualPaddings = root.attributeNode().getFloatList("visualPadding"); + if (visualPaddings.length == 4) { + icon.setVisualPadding(new Insets( + (int) visualPaddings[0], + (int) visualPaddings[1], + (int) visualPaddings[2], + (int) visualPaddings[3])); + } + } +} diff --git a/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/IconColorMapper.java b/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/IconColorMapper.java index a51676af..689c9276 100644 --- a/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/IconColorMapper.java +++ b/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/IconColorMapper.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2019-2021 Jannis Weis + * Copyright (c) 2019-2022 Jannis Weis * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, @@ -22,24 +22,15 @@ package com.github.weisj.darklaf.properties.icons; import java.awt.*; import java.util.*; -import java.util.List; -import java.util.logging.Level; import java.util.logging.Logger; -import javax.swing.*; - import com.github.weisj.darklaf.properties.PropertyLoader; import com.github.weisj.darklaf.properties.parser.ParseResult; import com.github.weisj.darklaf.properties.parser.Parser; import com.github.weisj.darklaf.properties.parser.ParserContext; -import com.github.weisj.darklaf.util.ColorUtil; import com.github.weisj.darklaf.util.LogUtil; import com.github.weisj.darklaf.util.Pair; import com.github.weisj.darklaf.util.Types; -import com.kitfox.svg.*; -import com.kitfox.svg.animation.AnimationElement; -import com.kitfox.svg.app.beans.SVGIcon; -import com.kitfox.svg.xml.StyleAttribute; /** * Utility class responsible for patching color definitions in svg icons. @@ -47,156 +38,23 @@ import com.kitfox.svg.xml.StyleAttribute; * @author Jannis Weis */ public final class IconColorMapper { - private static final String INLINE_VALUE_PREFIX = "%"; private static final Logger LOGGER = LogUtil.getLogger(IconLoader.class); + private static final String INLINE_VALUE_PREFIX = "%"; private static final Color FALLBACK_COLOR = Color.RED; - public static void patchColors(final SVGIcon svgIcon) { - patchColors(svgIcon, UIManager.getDefaults()); - } - - public static void patchColors(final SVGIcon svgIcon, final Map contextDefaults) { - patchColors(svgIcon, contextDefaults, null); - } - - public static void patchColors(final SVGIcon svgIcon, final Map defaults, - final Map contextDefaults) { - SVGUniverse universe = svgIcon.getSvgUniverse(); - SVGDiagram diagram = universe.getDiagram(svgIcon.getSvgURI()); - LOGGER.finer(() -> "Patching colors of icon " + svgIcon.getSvgURI()); - try { - loadColors(diagram, defaults, contextDefaults); - } catch (final SVGElementException e) { - LOGGER.log(Level.SEVERE, "Failed patching colors. " + e.getMessage(), e); - } - } - - private static void loadColors(final SVGDiagram diagram, final Map defaults, - final Map contextDefaults) - throws SVGElementException { - SVGRoot root = diagram.getRoot(); - SVGElement defs = diagram.getElement("colors"); - if (defs == null) { - LOGGER.info(() -> { - String uri = diagram.getXMLBase().toASCIIString(); - String name = uri.substring(Math.min(uri.lastIndexOf('/') + 1, uri.length() - 1)); - return "Themed icon '" + name - + "' has no color definitions. Consider loading it as a standard icon or add missing definitions"; - }); - return; - } - List children = defs.getChildren(null); - root.removeChild(defs); - - Defs themedDefs = new Defs(); - themedDefs.addAttribute("id", AnimationElement.AT_XML, "colors"); - root.loaderAddChild(null, themedDefs); - - for (SVGElement child : children) { - if (child instanceof LinearGradient) { - LinearGradient grad = (LinearGradient) child; - String id = grad.getId(); - StyleAttribute colorFallbacks = getAttribute("fallback", grad); - StyleAttribute opacityFallbacks = getAttribute("opacity-fallback", grad); - String opacityKey = getOpacityKey(grad); - - float opacity = getOpacity(opacityKey, getFallbacks(opacityFallbacks), defaults, contextDefaults); - float opacity1 = opacity; - float opacity2 = opacity; - if (opacity < 0) { - opacity = 1; - int childCount = grad.getNumChildren(); - if (childCount > 0) { - SVGElement elem = grad.getChild(0); - if (elem instanceof Stop) { - opacity1 = getStopOpacity((Stop) elem); - } - } - if (childCount > 1) { - SVGElement elem = grad.getChild(1); - if (elem instanceof Stop) { - opacity2 = getStopOpacity((Stop) elem); - } - } - - if (opacity1 < 0) opacity1 = opacity; - if (opacity2 < 0) opacity2 = opacity; - } - - Color c = resolveColor(id, getFallbacks(colorFallbacks), FALLBACK_COLOR, defaults, contextDefaults); - float finalOpacity1 = opacity1; - float finalOpacity2 = opacity2; - LOGGER.finest(() -> "Color: " + c + " opacity1: " + finalOpacity1 + " opacity2: " + finalOpacity2); - ColorResult result = - createColor(c, id, opacityKey, new StyleAttribute[] {colorFallbacks, opacityFallbacks}, - finalOpacity2, opacity2); - themedDefs.loaderAddChild(null, result.gradient); - result.finalizer.run(); - int resultRGB = ColorUtil.rgbNoAlpha(result.gradient.getStopColors()[0]); - int expectedRGB = ColorUtil.rgbNoAlpha(result.color); - if (expectedRGB != resultRGB) { - throw new IllegalStateException("Color not applied. Expected " + result.color + " but received " - + result.gradient.getStopColors()[0] + " (rgb " + expectedRGB + " != " + resultRGB + ")"); - } - LOGGER.finest(() -> Arrays.toString(result.gradient.getStopColors())); - } - } - LOGGER.fine("Patching done"); - } - - public static float getOpacity(final LinearGradient gradient, final Map propertyMap, - final Map contextDefaults) { - String opacityKey = getOpacityKey(gradient); - return getOpacity(opacityKey, null, propertyMap, contextDefaults); - } - - public static Color getColor(final LinearGradient gradient, final Map propertyMap, - final Map contextDefaults) { - String id = gradient.getId(); - StyleAttribute fallbacks = getAttribute("fallback", gradient); - return resolveColor(id, getFallbacks(fallbacks), FALLBACK_COLOR, propertyMap, contextDefaults); - } - - private static Color resolveColor(final String key, final String[] fallbacks, final Color fallbackColor, + public static Color resolveColor(final String key, final String[] fallbacks, final Map propertyMap, final Map contextDefaults) { Color color = get(propertyMap, contextDefaults, key, fallbacks, Color.class); if (color == null) { - color = fallbackColor; + color = FALLBACK_COLOR; LOGGER.warning("Could not load color with id '" + key + "' fallbacks" + Arrays.toString(fallbacks) - + ". Using color '" + fallbackColor + "' instead."); + + ". Using color '" + color + "' instead."); } return color; } - private static StyleAttribute getAttribute(final String key, final SVGElement child) { - StyleAttribute attribute = new StyleAttribute(); - attribute.setName(key); - try { - child.getStyle(attribute); - } catch (final SVGException e) { - return null; - } - return attribute; - } - - private static float getStopOpacity(final Stop stop) { - StyleAttribute attribute = new StyleAttribute(); - attribute.setName("stop-opacity"); - try { - stop.getStyle(attribute); - } catch (final SVGException e) { - return -1; - } - return !attribute.getStringValue().isEmpty() ? attribute.getFloatValue() : -1; - } - - private static String[] getFallbacks(final StyleAttribute fallbacks) { - if (fallbacks == null) return new String[0]; - return fallbacks.getStringList(); - } - - private static float getOpacity(final String key, final String[] fallbacks, final Map propertyMap, + public static float getOpacity(final String key, final String[] fallbacks, final Map propertyMap, final Map contextDefaults) { if ((key == null || key.isEmpty()) && (fallbacks == null || fallbacks.length == 0)) return -1; // UIManager defaults to 0, if the value isn't an integer (or null). @@ -215,124 +73,6 @@ public final class IconColorMapper { return -1; } - private static String getOpacityKey(final LinearGradient child) { - StyleAttribute attribute = new StyleAttribute(); - attribute.setName("opacity"); - try { - child.getStyle(attribute); - } catch (final SVGException e) { - e.printStackTrace(); - return null; - } - return attribute.getStringValue(); - } - - private static class ColorResult { - private final LinearGradient gradient; - private final Runnable finalizer; - private final Color color; - - private ColorResult(LinearGradient gradient, Runnable finalizer, Color color) { - this.gradient = gradient; - this.finalizer = finalizer; - this.color = color; - } - } - - private static ColorResult createColor(final Color c, final String name, final String opacityKey, - final StyleAttribute[] extraAttributes, final float opacity1, final float opacity2) - throws SVGElementException { - LinearGradient grad = new LinearGradient(); - grad.addAttribute("id", AnimationElement.AT_XML, name); - if (opacityKey != null && !opacityKey.isEmpty()) { - grad.addAttribute("opacity", AnimationElement.AT_XML, opacityKey); - } - if (extraAttributes != null) { - for (StyleAttribute attribute : extraAttributes) { - if (attribute != null && !attribute.getStringValue().isEmpty()) { - grad.addAttribute(attribute.getName(), AnimationElement.AT_XML, attribute.getStringValue()); - } - } - } - return new ColorResult(grad, () -> { - String color = toHexString(c); - BuildableStop stop1 = new BuildableStop(color); - BuildableStop stop2 = new BuildableStop(color); - try { - stop1.addAttribute("stop-color", AnimationElement.AT_XML, color); - stop1.addAttribute("offset", AnimationElement.AT_XML, "0"); - stop2.addAttribute("stop-color", AnimationElement.AT_XML, color); - stop2.addAttribute("offset", AnimationElement.AT_XML, "1"); - if (opacity1 != 1) { - stop1.addAttribute("stop-opacity", AnimationElement.AT_XML, String.valueOf(opacity1)); - } - if (opacity2 != 1) { - stop2.addAttribute("stop-opacity", AnimationElement.AT_XML, String.valueOf(opacity2)); - } - grad.loaderAddChild(null, stop1); - grad.loaderAddChild(null, stop2); - stop1.build(); - stop2.build(); - } catch (final SVGException e) { - throw new RuntimeException(e); - } - }, ColorUtil.toAlpha(c, opacity1)); - } - - private static class BuildableStop extends Stop { - - private final String color; - - private BuildableStop(final String color) { - this.color = color; - } - - @Override - public boolean getStyle(StyleAttribute attrib, boolean recursive, boolean evalAnimation) throws SVGException { - if ("stop-color".equals(attrib.getName())) { - attrib.setStringValue(color); - return true; - } - return super.getStyle(attrib, recursive, evalAnimation); - } - - @Override - protected void build() throws SVGException { - super.build(); - } - } - - public static Map getProperties(final SVGIcon svgIcon) { - SVGUniverse universe = svgIcon.getSvgUniverse(); - SVGDiagram diagram = universe.getDiagram(svgIcon.getSvgURI()); - SVGElement defs = diagram.getElement("colors"); - Map values = new HashMap<>(); - if (defs != null) { - List children = defs.getChildren(null); - for (SVGElement child : children) { - if (child instanceof LinearGradient) { - LinearGradient grad = (LinearGradient) child; - String colorKey = grad.getId(); - String opacityKey = getOpacityKey(grad); - SVGElement c = grad.getChild(0); - if (c instanceof Stop) { - Stop stop = (Stop) c; - StyleAttribute colorAttr = getAttribute("stop-color", stop); - Color color = colorAttr != null ? colorAttr.getColorValue() : null; - values.put(colorKey, color != null ? color : Color.BLACK); - - if (opacityKey != null && !opacityKey.isEmpty()) { - StyleAttribute opacityAttr = getAttribute("stop-opacity", stop); - int opacity = opacityAttr != null ? (int) (100 * opacityAttr.getFloatValue()) : 100; - values.put(opacityKey, opacity); - } - } - } - } - } - return values; - } - public static Pair getEntry(final Map map, final Map contextDefaults, final Object key, final Object[] fallbacks, final Class type) { Object obj = null; @@ -366,7 +106,7 @@ public final class IconColorMapper { // We found the value break outer; } else { - // Further search won't find anything. + // Any further searches won't find anything. // The value doesn't explicitly reference other keys. continue outer; } @@ -381,8 +121,4 @@ public final class IconColorMapper { final Object[] fallbacks, final Class type) { return getEntry(map, contextDefaults, key, fallbacks, type).getSecond(); } - - private static String toHexString(final Color color) { - return "#" + ColorUtil.toHex(color); - } } diff --git a/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/IconLoader.java b/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/IconLoader.java index da59dd6d..80256a53 100644 --- a/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/IconLoader.java +++ b/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/IconLoader.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2019-2021 Jannis Weis + * Copyright (c) 2019-2022 Jannis Weis * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, @@ -28,8 +28,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; -import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.*; @@ -41,6 +39,7 @@ import org.jetbrains.annotations.Nullable; import com.github.weisj.darklaf.util.LazyValue; import com.github.weisj.darklaf.util.LogUtil; import com.github.weisj.darklaf.util.cache.SoftCache; +import com.github.weisj.jsvg.parser.SVGLoader; /** * Default implementation of {@link IconResolver}, which provides some additional convenience @@ -56,6 +55,8 @@ public final class IconLoader implements IconResolver { private static final AtomicReference currentThemeKey = new AtomicReference<>(null); private static final AtomicReference currentAwareStyle = new AtomicReference<>(null); + private static final SVGLoader loader; + // Infer size by default. private static final int DEFAULT_WIDTH_SVG = -1; private static final int DEFAULT_HEIGHT_SVG = -1; @@ -73,6 +74,11 @@ public final class IconLoader implements IconResolver { updateThemeStatus(new Object()); } }); + loader = new SVGLoader(); + } + + static SVGLoader svgLoader() { + return loader; } /** @@ -415,35 +421,26 @@ public final class IconLoader implements IconResolver { private CacheableIcon loadSVGIconInternal(final String path, final int w, final int h, final boolean themed, final Map propertyMap) { - Supplier uriSupplier = createURISupplier(path); + URI uri = createURI(path); DarkSVGIcon svgIcon; if (themed) { if (propertyMap != null) { - svgIcon = new CustomThemedIcon(uriSupplier, w, h, propertyMap); + svgIcon = new CustomThemedIcon(uri, w, h, propertyMap); } else { - svgIcon = new ThemedSVGIcon(uriSupplier, w, h); + svgIcon = new ThemedSVGIcon(uri, w, h); } } else { - svgIcon = new DarkSVGIcon(createURISupplier(path), w, h); + svgIcon = new DarkSVGIcon(uri, w, h); } return svgIcon; } - private Supplier createURISupplier(final String path) { - return () -> { - try { - URL url = getResource(path); - if (url.getPath().startsWith("file:///")) { - return new URI(url.toString().replace("file:///", "file:/")); - } else { - return Objects.requireNonNull(url.toURI()); - } - } catch (NullPointerException | URISyntaxException e) { - LOGGER.log(Level.SEVERE, - "Exception while loading '" + path + "'" + ". Resolving from " + parentClass, e); - } - return null; - }; + private @NotNull URI createURI(final String path) { + try { + return Objects.requireNonNull(getResource(path)).toURI(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } } /** diff --git a/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/ThemedSVGIcon.java b/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/ThemedSVGIcon.java index 63168bba..e4a6c987 100644 --- a/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/ThemedSVGIcon.java +++ b/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/ThemedSVGIcon.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2019-2021 Jannis Weis + * Copyright (c) 2019-2022 Jannis Weis * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, @@ -21,10 +21,15 @@ package com.github.weisj.darklaf.properties.icons; import java.net.URI; -import java.util.function.Supplier; +import java.util.ArrayList; +import java.util.List; import javax.swing.*; +import org.jetbrains.annotations.NotNull; + +import com.github.weisj.jsvg.parser.*; + /** * An {@link DarkSVGIcon}, which will patch dynamic color values defined in the svg file. @@ -33,21 +38,18 @@ import javax.swing.*; */ public class ThemedSVGIcon extends DarkSVGIcon implements ThemedIcon { + private final List paints; private Object currentTheme; private boolean updatedNotDuringPaint; - public ThemedSVGIcon(final Supplier uriSupplier, final int displayWidth, final int displayHeight) { - super(uriSupplier, displayWidth, displayHeight); - currentTheme = new Object(); - } - public ThemedSVGIcon(final URI uri, final int displayWidth, final int displayHeight) { super(uri, displayWidth, displayHeight); - currentTheme = new Object(); + this.paints = new ArrayList<>(); } protected ThemedSVGIcon(final int width, final int height, final ThemedSVGIcon icon) { super(width, height, icon); + this.paints = icon.paints; this.currentTheme = icon.currentTheme; this.updatedNotDuringPaint = icon.updatedNotDuringPaint; } @@ -83,22 +85,36 @@ public class ThemedSVGIcon extends DarkSVGIcon implements ThemedIcon { if (updatedNotDuringPaint) { updatedNotDuringPaint = false; // Update didn't happen during painting call. - // Image might not be up to date. + // Image might not be up-to-date. return true; } return false; } + @Override + protected @NotNull ParserProvider createParserProvider() { + return new ThemedSVGIconParserProvider(this); + } + @Override public String toString() { return "ThemedSVGIcon{" + "parentState= " + super.toString() + - "currentTheme=" + currentTheme + + "paints=" + paints + + ", currentTheme=" + currentTheme + ", updatedNotDuringPaint=" + updatedNotDuringPaint + '}'; } - protected void invalidate() { + void registerPaint(final ThemedSVGIconParserProvider.ThemedSolidColorPaint paint) { + paints.add(paint); + } + + List paints() { + return paints; + } + + public void invalidate() { currentTheme = new Object(); } @@ -107,6 +123,6 @@ public class ThemedSVGIcon extends DarkSVGIcon implements ThemedIcon { } protected void patchColors() { - IconColorMapper.patchColors(getSVGIcon(), getContextDefaults()); + ThemedSVGIconParserProvider.patchColors(paints, getContextDefaults(), null); } } diff --git a/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/ThemedSVGIconParserProvider.java b/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/ThemedSVGIconParserProvider.java new file mode 100644 index 00000000..09ca27d9 --- /dev/null +++ b/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/ThemedSVGIconParserProvider.java @@ -0,0 +1,158 @@ +/* + * MIT License + * + * Copyright (c) 2021-2022 Jannis Weis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.github.weisj.darklaf.properties.icons; + +import java.awt.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.github.weisj.darklaf.util.ColorUtil; +import com.github.weisj.jsvg.attributes.paint.DefaultPaintParser; +import com.github.weisj.jsvg.attributes.paint.SimplePaintSVGPaint; +import com.github.weisj.jsvg.nodes.Defs; +import com.github.weisj.jsvg.nodes.LinearGradient; +import com.github.weisj.jsvg.parser.AttributeNode; +import com.github.weisj.jsvg.parser.DefaultParserProvider; +import com.github.weisj.jsvg.parser.DomProcessor; +import com.github.weisj.jsvg.parser.ParsedElement; + +public class ThemedSVGIconParserProvider extends DefaultParserProvider { + private final DomProcessor preProcessor; + + public ThemedSVGIconParserProvider(final @NotNull ThemedSVGIcon icon) { + preProcessor = new ThemedSVGIconDomProcessor(icon); + } + + @Override + public @Nullable DomProcessor createPreProcessor() { + return preProcessor; + } + + private static class ThemedSVGIconDomProcessor extends DarkSVGIconDomProcessor { + + public ThemedSVGIconDomProcessor(@NotNull ThemedSVGIcon icon) { + super(icon); + } + + @Override + public void process(final @NotNull ParsedElement root) { + super.process(root); + for (ParsedElement child : root.children()) { + if ("colors".equals(child.id()) && child.node() instanceof Defs) { + replaceGradients(child); + return; + } + } + } + + private void replaceGradients(final ParsedElement element) { + for (ParsedElement child : element.children()) { + if (child.node() instanceof LinearGradient) { + replaceSingleGradient(child); + } + } + } + + private void replaceSingleGradient(final ParsedElement gradient) { + String id = gradient.id(); + if (id == null) return; + + AttributeNode attributeNode = gradient.attributeNode(); + String[] fallbacks = attributeNode.getStringList("fallback"); + String opacityKey = attributeNode.getValue("opacity"); + String[] opacityFallback = attributeNode.getStringList("opacity-fallback"); + + List stops = gradient.children(); + float originalOpacity = 1; + if (!stops.isEmpty()) { + originalOpacity = stops.get(0).attributeNode().getPercentage("stop-opacity", originalOpacity); + } + + ThemedSVGIconParserProvider.ThemedSolidColorPaint themedPaint = + new ThemedSVGIconParserProvider.ThemedSolidColorPaint( + id, fallbacks, opacityKey, opacityFallback, originalOpacity); + gradient.registerNamedElement(id, themedPaint); + icon.registerPaint(themedPaint); + } + } + + public static void patchColors(final List paints, final Map propertyMap, + final Map contextDefaults) { + for (ThemedSolidColorPaint paint : paints) { + paint.color = IconColorMapper.resolveColor( + paint.colorKey, paint.colorFallbacks, propertyMap, contextDefaults); + float opacity = IconColorMapper.getOpacity( + paint.opacityKey, paint.opacityFallbacks, propertyMap, contextDefaults); + if (opacity < 0) opacity = paint.originalOpacity; + paint.color = ColorUtil.toAlpha(paint.color, opacity); + } + } + + public static Map getProperties(List paints) { + Map values = new HashMap<>(paints.size() * 2, 0.75f); + for (ThemedSVGIconParserProvider.ThemedSolidColorPaint paint : paints) { + values.put(paint.colorKey, ColorUtil.removeAlpha(paint.color)); + if (paint.opacityKey != null && !paint.opacityKey.isEmpty()) { + values.put(paint.opacityKey, (int) (paint.color.getAlpha() / 255f)); + } + } + return values; + } + + public static Map getNamedColors(final ThemedSVGIcon icon) { + icon.ensureLoaded(false); + return icon.paints().stream().collect(Collectors.toMap( + p -> p.colorKey, + p -> p.color)); + } + + static class ThemedSolidColorPaint implements SimplePaintSVGPaint { + + private final String colorKey; + private final String[] colorFallbacks; + private final String opacityKey; + private final String[] opacityFallbacks; + + private final float originalOpacity; + + private Color color = DefaultPaintParser.DEFAULT_COLOR; + + ThemedSolidColorPaint(final String colorKey, final String[] colorFallbacks, + final String opacityKey, final String[] opacityFallbacks, + float originalOpacity) { + this.colorKey = colorKey; + this.colorFallbacks = colorFallbacks; + this.opacityKey = opacityKey; + this.opacityFallbacks = opacityFallbacks; + this.originalOpacity = originalOpacity; + } + + @Override + public @NotNull Paint paint() { + return color; + } + } +} diff --git a/property-loader/src/main/module/module-info.java b/property-loader/src/main/module/module-info.java index 653f140a..ca1aef40 100644 --- a/property-loader/src/main/module/module-info.java +++ b/property-loader/src/main/module/module-info.java @@ -26,7 +26,7 @@ module darklaf.properties { requires transitive java.desktop; requires transitive darklaf.utils; requires swingdsl.visualPadding; - requires com.kitfox.svg; + requires com.github.weisj.jsvg; requires static org.jetbrains.annotations; diff --git a/property-loader/src/test/java/com/github/weisj/darklaf/properties/icons/SVGImageTest.java b/property-loader/src/test/java/com/github/weisj/darklaf/properties/icons/SVGImageTest.java index 56a2bc89..939157a3 100644 --- a/property-loader/src/test/java/com/github/weisj/darklaf/properties/icons/SVGImageTest.java +++ b/property-loader/src/test/java/com/github/weisj/darklaf/properties/icons/SVGImageTest.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2019-2021 Jannis Weis + * Copyright (c) 2019-2022 Jannis Weis * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, @@ -28,7 +28,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.ResourceLock; -import com.kitfox.svg.app.beans.SVGIcon; +import com.github.weisj.jsvg.SVGDocument; @ResourceLock(value = "IconLoader") class SVGImageTest { @@ -45,8 +45,8 @@ class SVGImageTest { DarkSVGIcon icon = (DarkSVGIcon) loader.getIcon("svg_icon.svg", 16, 16); Assertions.assertSame(icon, icon.derive(16, 16)); - // Force load the icon. - icon.getSVGIcon(); + // Forcefully load the icon. + icon.getSVGDocument(); icon.setDisplaySize(-1, -1); Assertions.assertSame(icon, loader.getIcon("svg_icon.svg", 16, 16)); @@ -62,7 +62,7 @@ class SVGImageTest { Assertions.assertSame(icon, loader.getIcon("svg_icon.svg", 16, 16)); // Force load - icon.getSVGIcon(); + icon.getSVGDocument(); Assertions.assertSame(icon, loader.getIcon("svg_icon.svg", 16, 16)); icon.setDisplaySize(50, 50); @@ -70,8 +70,8 @@ class SVGImageTest { icon.setDisplaySize(-1, -1); Assertions.assertNotSame(icon, loader.getIcon("svg_icon.svg", 50, 50)); - Assertions.assertSame(icon.getSVGIcon(), - ((DarkSVGIcon) loader.getIcon("svg_icon.svg", 50, 50)).getSVGIcon()); + Assertions.assertSame(icon.getSVGDocument(), + ((DarkSVGIcon) loader.getIcon("svg_icon.svg", 50, 50)).getSVGDocument()); Assertions.assertEquals(16, icon.getIconWidth()); @@ -86,17 +86,17 @@ class SVGImageTest { Assertions.assertEquals(16, icon.getIconWidth()); Assertions.assertEquals(16, icon.getIconHeight()); - Set svgSet = new HashSet<>(); + Set svgSet = new HashSet<>(); for (int i = 0; i < 100; i++) { DarkSVGIcon svgIcon = (DarkSVGIcon) loader.getIcon("svg_icon.svg"); - svgSet.add(svgIcon.getSVGIcon()); + svgSet.add(svgIcon.getSVGDocument()); } Assertions.assertEquals(1, svgSet.size()); svgSet.clear(); for (int i = 0; i < 100; i++) { DarkSVGIcon icon2 = icon.derive(i, i); - svgSet.add(icon2.getSVGIcon()); + svgSet.add(icon2.getSVGDocument()); } Assertions.assertEquals(1, svgSet.size()); } diff --git a/settings.gradle.kts b/settings.gradle.kts index 36e130e1..0b96f769 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,7 +32,7 @@ dependencyResolutionManagement { } create("libs") { - idv("svgSalamander", "com.formdev:svgSalamander") + idv("jsvg", "com.github.weisj:jsvg") idv("swingDslLafSupport", "com.github.weisj:swing-extensions-laf-support", "swingDsl") idv("visualPaddings", "com.github.weisj:swing-extensions-visual-padding", "swingDsl") idv("swingx", "org.swinglabs:swingx")