diff --git a/nbbuild/cluster.properties b/nbbuild/cluster.properties
index c850dcd0db19..b9a79066f948 100644
--- a/nbbuild/cluster.properties
+++ b/nbbuild/cluster.properties
@@ -189,6 +189,7 @@ nb.cluster.platform=\
libs.jna,\
libs.jna.platform,\
libs.jsr223,\
+ libs.jsvg,\
libs.junit4,\
libs.junit5,\
libs.osgi,\
diff --git a/nbbuild/licenses/MIT-jsvg b/nbbuild/licenses/MIT-jsvg
new file mode 100644
index 000000000000..f3a3d71f2de5
--- /dev/null
+++ b/nbbuild/licenses/MIT-jsvg
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021-2024 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.
diff --git a/nbbuild/licenses/names.properties b/nbbuild/licenses/names.properties
index 36ea7e99937c..0cd108da0ea5 100644
--- a/nbbuild/licenses/names.properties
+++ b/nbbuild/licenses/names.properties
@@ -69,6 +69,7 @@ MIT-phpstan=MIT license PHPStan variant
MIT-validator=MIT license validator variant
MIT-vscode=MIT license for VSCode
MIT-vscode-ext=MIT license VS Code variant
+MIT-jsvg=MIT license JSVG variant
MPL-1.0=MPL 1.0 license
CDDL-SPL=CDDL 1.0 + SPL 1.0
W3C2=W3C Software and Document Notice and License
diff --git a/platform/libs.jsvg/build.xml b/platform/libs.jsvg/build.xml
new file mode 100644
index 000000000000..70b06619279c
--- /dev/null
+++ b/platform/libs.jsvg/build.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+ Builds the JSVG library wrapper module.
+
+
diff --git a/platform/libs.jsvg/external/binaries-list b/platform/libs.jsvg/external/binaries-list
new file mode 100644
index 000000000000..9076715ac7c0
--- /dev/null
+++ b/platform/libs.jsvg/external/binaries-list
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+A84D34480044DB51A1448E9C0E32BD284B797288 com.github.weisj:jsvg:1.6.1
diff --git a/platform/libs.jsvg/external/jsvg-1.6.1-license.txt b/platform/libs.jsvg/external/jsvg-1.6.1-license.txt
new file mode 100644
index 000000000000..3b8f0f662212
--- /dev/null
+++ b/platform/libs.jsvg/external/jsvg-1.6.1-license.txt
@@ -0,0 +1,29 @@
+Name: JSVG
+Version: 1.6.1
+Description: JSVG - A Java SVG implementation
+License: MIT-jsvg
+Origin: JSVG
+URL: https://github.com/weisJ/jsvg
+Files: jsvg-1.6.1.jar
+
+MIT License
+
+Copyright (c) 2021-2024 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.
diff --git a/platform/libs.jsvg/manifest.mf b/platform/libs.jsvg/manifest.mf
new file mode 100644
index 000000000000..c0657cdc1e4c
--- /dev/null
+++ b/platform/libs.jsvg/manifest.mf
@@ -0,0 +1,5 @@
+Manifest-Version: 1.0
+OpenIDE-Module: org.netbeans.libs.jsvg
+OpenIDE-Module-Implementation-Version: 1
+OpenIDE-Module-Localizing-Bundle: org/netbeans/libs/jsvg/Bundle.properties
+AutoUpdate-Show-In-Client: false
diff --git a/platform/libs.jsvg/nbproject/project.properties b/platform/libs.jsvg/nbproject/project.properties
new file mode 100644
index 000000000000..6fb34845af4e
--- /dev/null
+++ b/platform/libs.jsvg/nbproject/project.properties
@@ -0,0 +1,26 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+file.reference.jsvg-1.6.1.jar=external/jsvg-1.6.1.jar
+release.external/jsvg-1.6.1.jar=modules/ext/jsvg-1.6.1.jar
+
+is.autoload=true
+javac.source=1.8
+
+nbm.homepage=https://github.com/weisJ/jsvg
+sigtest.gen.fail.on.error=false
+spec.version.base=1.22.0
diff --git a/platform/libs.jsvg/nbproject/project.xml b/platform/libs.jsvg/nbproject/project.xml
new file mode 100644
index 000000000000..0053019fd856
--- /dev/null
+++ b/platform/libs.jsvg/nbproject/project.xml
@@ -0,0 +1,64 @@
+
+
+
+ org.netbeans.modules.apisupport.project
+
+
+ org.netbeans.libs.jsvg
+
+
+ com.github.weisj.jsvg
+ com.github.weisj.jsvg.attributes
+ com.github.weisj.jsvg.attributes.filter
+ com.github.weisj.jsvg.attributes.font
+ com.github.weisj.jsvg.attributes.paint
+ com.github.weisj.jsvg.attributes.stroke
+ com.github.weisj.jsvg.attributes.text
+ com.github.weisj.jsvg.geometry
+ com.github.weisj.jsvg.geometry.mesh
+ com.github.weisj.jsvg.geometry.noise
+ com.github.weisj.jsvg.geometry.path
+ com.github.weisj.jsvg.geometry.size
+ com.github.weisj.jsvg.geometry.util
+ com.github.weisj.jsvg.nodes
+ com.github.weisj.jsvg.nodes.animation
+ com.github.weisj.jsvg.nodes.container
+ com.github.weisj.jsvg.nodes.filter
+ com.github.weisj.jsvg.nodes.mesh
+ com.github.weisj.jsvg.nodes.prototype
+ com.github.weisj.jsvg.nodes.prototype.spec
+ com.github.weisj.jsvg.nodes.text
+ com.github.weisj.jsvg.parser
+ com.github.weisj.jsvg.parser.css
+ com.github.weisj.jsvg.parser.resources
+ com.github.weisj.jsvg.renderer
+ com.github.weisj.jsvg.renderer.awt
+ com.github.weisj.jsvg.renderer.jdk
+ com.github.weisj.jsvg.util
+
+
+ ext/jsvg-1.6.1.jar
+ external/jsvg-1.6.1.jar
+
+
+
+
diff --git a/platform/libs.jsvg/src/org/netbeans/libs/jsvg/Bundle.properties b/platform/libs.jsvg/src/org/netbeans/libs/jsvg/Bundle.properties
new file mode 100644
index 000000000000..cc006f2d790e
--- /dev/null
+++ b/platform/libs.jsvg/src/org/netbeans/libs/jsvg/Bundle.properties
@@ -0,0 +1,22 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+OpenIDE-Module-Name=JSVG Library
+OpenIDE-Module-Short-Description=JSVG Library
+OpenIDE-Module-Long-Description=Contains the JSVG library, for reading and painting SVG files.
+OpenIDE-Module-Display-Category=Libraries
diff --git a/platform/openide.util.ui.svg/nbproject/project.xml b/platform/openide.util.ui.svg/nbproject/project.xml
index 851ebd109a70..251685f9ec8d 100644
--- a/platform/openide.util.ui.svg/nbproject/project.xml
+++ b/platform/openide.util.ui.svg/nbproject/project.xml
@@ -26,7 +26,7 @@
org.openide.util.ui.svg
- org.netbeans.libs.batik.read
+ org.netbeans.libs.jsvg
diff --git a/platform/openide.util.ui.svg/src/org/openide/util/svg/DenyingElementLoader.java b/platform/openide.util.ui.svg/src/org/openide/util/svg/DenyingElementLoader.java
new file mode 100644
index 000000000000..6fbd29ce7052
--- /dev/null
+++ b/platform/openide.util.ui.svg/src/org/openide/util/svg/DenyingElementLoader.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.openide.util.svg;
+
+import com.github.weisj.jsvg.attributes.AttributeParser;
+import com.github.weisj.jsvg.parser.ElementLoader;
+import com.github.weisj.jsvg.parser.ParsedDocument;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+class DenyingElementLoader implements ElementLoader {
+ private final Set attemptedExternalURLsLoaded = new LinkedHashSet<>();
+
+ public Set getAttemptedExternalURLsLoaded() {
+ return Collections.unmodifiableSet(attemptedExternalURLsLoaded);
+ }
+
+ @Override
+ public T loadElement(Class type, String value,
+ ParsedDocument document, AttributeParser attributeParser)
+ {
+ /* Same logic as in com.github.weisj.jsvg.parser.DefaultElementLoader for the
+ AllowExternalResources.DENY case, but gathering up the attempted externally loaded URLs so
+ we can make the whole loading operation fail and make testLoadImageWithExternalUseXlinkHref
+ pass. */
+ String url = attributeParser.parseUrl(value);
+ if (url == null) {
+ return null;
+ }
+ if (url.contains("#")) {
+ String[] parts = url.split("#", 2);
+ String name = parts[0];
+ if (!name.isEmpty()) {
+ attemptedExternalURLsLoaded.add(value);
+ return null;
+ }
+ return document.getElementById(type, parts[1]);
+ } else {
+ return document.getElementById(type, url);
+ }
+ }
+}
diff --git a/platform/openide.util.ui.svg/src/org/openide/util/svg/SVGIcon.java b/platform/openide.util.ui.svg/src/org/openide/util/svg/SVGIcon.java
index 1d5b227c5c75..48a04ec64b49 100644
--- a/platform/openide.util.ui.svg/src/org/openide/util/svg/SVGIcon.java
+++ b/platform/openide.util.ui.svg/src/org/openide/util/svg/SVGIcon.java
@@ -23,7 +23,6 @@
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
-import java.awt.geom.Dimension2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.IOException;
@@ -35,21 +34,17 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Icon;
-import org.apache.batik.anim.dom.SAXSVGDocumentFactory;
-import org.apache.batik.bridge.BridgeContext;
-import org.apache.batik.bridge.DocumentLoader;
-import org.apache.batik.bridge.ExternalResourceSecurity;
-import org.apache.batik.bridge.GVTBuilder;
-import org.apache.batik.bridge.NoLoadExternalResourceSecurity;
-import org.apache.batik.bridge.UserAgent;
-import org.apache.batik.bridge.UserAgentAdapter;
-import org.apache.batik.ext.awt.image.GraphicsUtil;
-import org.apache.batik.gvt.GraphicsNode;
-import org.apache.batik.util.ParsedURL;
-import org.apache.batik.util.XMLResourceDescriptor;
+import com.github.weisj.jsvg.SVGDocument;
+import com.github.weisj.jsvg.geometry.size.FloatSize;
+import com.github.weisj.jsvg.parser.LoaderContext;
+import com.github.weisj.jsvg.parser.ResourceLoader;
+import com.github.weisj.jsvg.parser.SVGLoader;
+import com.github.weisj.jsvg.renderer.awt.NullPlatformSupport;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
import org.openide.util.CachedHiDPIIcon;
import org.openide.util.Parameters;
-import org.w3c.dom.Document;
/**
* An icon loaded from an SVG file resource. Renders in high resolution on HiDPI displays.
@@ -62,62 +57,64 @@ final class SVGIcon extends CachedHiDPIIcon {
enough to avoid an OutOfMemoryError but large enough to cater for most SVG loading scenarios.
Photoshop had 10000 pixels as a maximum limit for many years. */
private static final int MAX_DIMENSION_PIXELS = 8192;
- // XML document factories are expensive to initialize, so do it once per thread only.
- private static final ThreadLocal DOCUMENT_FACTORY =
- new ThreadLocal()
+ /* XML document factories are expensive to initialize, so do it once per thread only. This
+ optimization was originally done for the Batik SVG library, but I suspect it might be beneficial
+ for JSVG as well. The SVGLoader constructor does initialize some XML parser stuff. */
+ private static final ThreadLocal SVG_LOADER =
+ new ThreadLocal()
{
@Override
- protected SAXSVGDocumentFactory initialValue() {
- return new SAXSVGDocumentFactory(XMLResourceDescriptor.getXMLParserClassName());
+ protected SVGLoader initialValue() {
+ return new SVGLoader();
}
};
private final URL url;
/**
- * Cache of the parsed SVG document. Just painting the GraphicsNode is much faster than also
+ * Cache of the parsed SVG document. Just painting the SVGDocument is probably faster than also
* re-parsing the underlying SVG file, yet we want to avoid keeping potentially complex object
- * trees in memory for the lifetime of the Icon instance. Thus we allow the GraphicsNode to be
+ * trees in memory for the lifetime of the Icon instance. Thus we allow the SVGDocument to be
* garbage collected after the first paint. The rasterized bitmap will be cached separately by
* the superclass.
*/
- private WeakReference graphicsNodeWeakRef;
+ private WeakReference svgDocumentWeakRef;
/**
- * A strong reference version of {@link #graphicsNodeWeakRef}, which can be set to ensure that
- * the latter is not yet garbage collected. Used to ensure that the initially loaded
- * GraphicsNode is cached at least until the first time the icon is painted. May be null.
+ * A strong reference version of {@link #svgDocumentWeakRef}, which can be set to ensure that
+ * the latter is not yet garbage collected. Used to ensure that the initially loaded SVGDocument
+ * is cached at least until the first time the icon is painted. May be null.
*/
- private GraphicsNode graphicsNodeStrongRef;
+ private SVGDocument svgDocumentStrongRef;
- private SVGIcon(URL url, GraphicsNode initialGraphicsNode, int width, int height) {
+ private SVGIcon(URL url, SVGDocument initialSVGDocument, int width, int height) {
super(width, height);
Parameters.notNull("url", url);
- Parameters.notNull("initialGraphicsNode", initialGraphicsNode);
+ Parameters.notNull("initialSVGDocument", initialSVGDocument);
this.url = url;
- this.graphicsNodeStrongRef = initialGraphicsNode;
- this.graphicsNodeWeakRef = new WeakReference(initialGraphicsNode);
+ this.svgDocumentStrongRef = initialSVGDocument;
+ this.svgDocumentWeakRef = new WeakReference(initialSVGDocument);
}
public static Icon load(URL url) throws IOException {
Parameters.notNull("url", url);
Dimension size = new Dimension();
- GraphicsNode initialGraphicsNode = loadGraphicsNode(url, size);
- return new SVGIcon(url, initialGraphicsNode, size.width, size.height);
+ SVGDocument initialSVGDocument = loadSVGDocument(url, size);
+ return new SVGIcon(url, initialSVGDocument, size.width, size.height);
}
/**
- * Get the {@code GraphicsNode}, re-loading it from the original resource if a cached instance
+ * Get the {@code SVGDocument}, re-loading it from the original resource if a cached instance
* is no longer available. Once this method has been called at least once, garbage collection
* may cause the cache to be cleared.
*/
- private synchronized GraphicsNode getGraphicsNode() throws IOException {
- GraphicsNode ret = graphicsNodeWeakRef.get();
+ private synchronized SVGDocument getSVGDocument() throws IOException {
+ SVGDocument ret = svgDocumentWeakRef.get();
if (ret != null) {
- // Allow the GraphicsNode to be garbage collected after the initial paint.
- graphicsNodeStrongRef = null;
+ // Allow the SVGDocument to be garbage collected after the initial paint.
+ svgDocumentStrongRef = null;
return ret;
}
- ret = loadGraphicsNode(url, null);
- graphicsNodeWeakRef = new WeakReference(ret);
+ ret = loadSVGDocument(url, null);
+ svgDocumentWeakRef = new WeakReference(ret);
return ret;
}
@@ -126,40 +123,50 @@ private synchronized GraphicsNode getGraphicsNode() throws IOException {
*
* @param toSize if not null, will be set to the image's size
*/
- private static GraphicsNode loadGraphicsNode(URL url, Dimension toSize)
- throws IOException
- {
+ private static SVGDocument loadSVGDocument(URL url, Dimension toSize) throws IOException {
Parameters.notNull("url", url);
- final GraphicsNode graphicsNode;
- final Dimension2D documentSize;
- final Document doc;
+
+ final SVGDocument svgDocument;
+ FloatSize documentSize;
InputStream is = url.openStream();
try {
- // See http://batik.2283329.n4.nabble.com/rendering-directly-to-java-awt-Graphics2D-td3716202.html
- SAXSVGDocumentFactory factory = DOCUMENT_FACTORY.get();
- /* Don't provide an URI here; we shouldn't commit to supporting relative links from
- loaded SVG documents. */
- doc = factory.createDocument(null, is);
- // Disallow external resource dereferences
- UserAgent userAgent = new UserAgentAdapter() {
- @Override
- public ExternalResourceSecurity getExternalResourceSecurity(
- ParsedURL resourceURL, ParsedURL docURL) {
- return new NoLoadExternalResourceSecurity();
- }
+ // Explicitly deny loading of external URLs.
+
+ /* Handle e.g. elements. Tested in
+ testLoadImageWithExternalImageHref. */
+ List externalResourceExceptions = new ArrayList<>();
+ ResourceLoader resourceLoader = (URI nnuri) -> {
+ IOException e = new IOException("External resource loading from SVG file not permitted ("+
+ nnuri + " from " + url + ")");
+ externalResourceExceptions.add(e);
+ throw e;
};
- DocumentLoader loader = new DocumentLoader(userAgent);
- BridgeContext bctx = new BridgeContext(userAgent, loader);
- try {
- bctx.setDynamicState(BridgeContext.STATIC);
- graphicsNode = new GVTBuilder().build(bctx, doc);
- documentSize = bctx.getDocumentSize();
- } finally {
- bctx.dispose();
+ /* Handle e.g.