diff --git a/bundles/org.eclipse.swt.svg/.classpath b/bundles/org.eclipse.swt.svg/.classpath
new file mode 100644
index 0000000000..d16b87e8ee
--- /dev/null
+++ b/bundles/org.eclipse.swt.svg/.classpath
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/bundles/org.eclipse.swt.svg/.project b/bundles/org.eclipse.swt.svg/.project
new file mode 100644
index 0000000000..587ce542c7
--- /dev/null
+++ b/bundles/org.eclipse.swt.svg/.project
@@ -0,0 +1,28 @@
+
+
+ org.eclipse.swt.svg
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/bundles/org.eclipse.swt.svg/.settings/org.eclipse.core.resources.prefs b/bundles/org.eclipse.swt.svg/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000000..99f26c0203
--- /dev/null
+++ b/bundles/org.eclipse.swt.svg/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/=UTF-8
diff --git a/bundles/org.eclipse.swt.svg/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.swt.svg/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000000..62ef3488cc
--- /dev/null
+++ b/bundles/org.eclipse.swt.svg/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,9 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
+org.eclipse.jdt.core.compiler.compliance=17
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=17
diff --git a/bundles/org.eclipse.swt.svg/META-INF/MANIFEST.MF b/bundles/org.eclipse.swt.svg/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..82af1dad47
--- /dev/null
+++ b/bundles/org.eclipse.swt.svg/META-INF/MANIFEST.MF
@@ -0,0 +1,10 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: SvgPlugin
+Bundle-SymbolicName: org.eclipse.swt.svg
+Bundle-Version: 1.0.0.qualifier
+Automatic-Module-Name: org.eclipse.swt.svgPlugin
+Bundle-RequiredExecutionEnvironment: JavaSE-17
+Export-Package: org.eclipse.swt.svg
+Import-Package: org.eclipse.swt.graphics
+Bundle-ClassPath: ., libs/jsvg-1.6.1.jar
diff --git a/bundles/org.eclipse.swt.svg/build.properties b/bundles/org.eclipse.swt.svg/build.properties
new file mode 100644
index 0000000000..3c7f762fff
--- /dev/null
+++ b/bundles/org.eclipse.swt.svg/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ libs/jsvg-1.6.1.jar
diff --git a/bundles/org.eclipse.swt.svg/libs/jsvg-1.6.1.jar b/bundles/org.eclipse.swt.svg/libs/jsvg-1.6.1.jar
new file mode 100644
index 0000000000..3095291a85
Binary files /dev/null and b/bundles/org.eclipse.swt.svg/libs/jsvg-1.6.1.jar differ
diff --git a/bundles/org.eclipse.swt.svg/src/org/eclipse/swt/svg/SVGRasterizer.java b/bundles/org.eclipse.swt.svg/src/org/eclipse/swt/svg/SVGRasterizer.java
new file mode 100644
index 0000000000..58483924eb
--- /dev/null
+++ b/bundles/org.eclipse.swt.svg/src/org/eclipse/swt/svg/SVGRasterizer.java
@@ -0,0 +1,73 @@
+package org.eclipse.swt.svg;
+
+import static java.awt.RenderingHints.*;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.io.*;
+import java.util.*;
+import org.eclipse.swt.graphics.ISVGRasterizer;
+import org.eclipse.swt.graphics.SVGRasterizerRegistry;
+import org.eclipse.swt.graphics.SVGUtil;
+
+import com.github.weisj.jsvg.*;
+import com.github.weisj.jsvg.geometry.size.*;
+import com.github.weisj.jsvg.parser.*;
+
+/**
+ * A rasterizer implementation for converting SVG data into rasterized images.
+ * This class implements the {@code ISVGRasterizer} interface.
+ *
+ * @since 1.0.0
+ */
+public class SVGRasterizer implements ISVGRasterizer {
+
+ private SVGLoader svgLoader;
+
+ /**
+ * Initializes the SVG rasterizer by registering an instance of this rasterizer
+ * with the {@link SVGRasterizerRegistry}.
+ */
+ public static void intializeSVGRasterizer() {
+ SVGRasterizerRegistry.register(new SVGRasterizer());
+ }
+
+ private final static Map RENDERING_HINTS = Map.of(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON, //
+ KEY_ALPHA_INTERPOLATION, VALUE_ALPHA_INTERPOLATION_QUALITY, //
+ KEY_COLOR_RENDERING, VALUE_COLOR_RENDER_QUALITY, //
+ KEY_DITHERING, VALUE_DITHER_DISABLE, //
+ KEY_FRACTIONALMETRICS, VALUE_FRACTIONALMETRICS_ON, //
+ KEY_INTERPOLATION, VALUE_INTERPOLATION_BICUBIC, //
+ KEY_RENDERING, VALUE_RENDER_QUALITY, //
+ KEY_STROKE_CONTROL, VALUE_STROKE_PURE, //
+ KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON //
+ );
+
+ @Override
+ public BufferedImage rasterizeSVG(byte[] bytes, float scalingFactor) throws IOException {
+ if(svgLoader == null) {
+ svgLoader = new SVGLoader();
+ }
+ SVGDocument svgDocument = null;
+ if (SVGUtil.isSVGFile(bytes)) {
+ try (InputStream stream = new ByteArrayInputStream(bytes)) {
+ svgDocument = svgLoader.load(stream, null, LoaderContext.createDefault());
+ }
+ if (svgDocument != null) {
+ FloatSize size = svgDocument.size();
+ double originalWidth = size.getWidth();
+ double originalHeight = size.getHeight();
+ int scaledWidth = (int) Math.round(originalWidth * scalingFactor);
+ int scaledHeight = (int) Math.round(originalHeight * scalingFactor);
+ BufferedImage image = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = image.createGraphics();
+ g.setRenderingHints(RENDERING_HINTS);
+ g.scale(scalingFactor, scalingFactor);
+ svgDocument.render(null, g);
+ g.dispose();
+ return image;
+ }
+ }
+ return null;
+ }
+}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/ImageLoader.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/ImageLoader.java
index 5b063ab2e7..68dff4b8b3 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/ImageLoader.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/ImageLoader.java
@@ -14,9 +14,12 @@
package org.eclipse.swt.graphics;
+import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
+import javax.imageio.ImageIO;
+
import org.eclipse.swt.*;
import org.eclipse.swt.internal.image.*;
@@ -149,10 +152,72 @@ void reset() {
*
*/
public ImageData[] load(InputStream stream) {
+ return loadDefault(stream);
+}
+
+private ImageData[] loadDefault(InputStream stream) {
if (stream == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
reset();
data = FileFormat.load(stream, this);
- return data;
+ return data;
+}
+
+/**
+ * Loads an array of ImageData objects from the
+ * specified input stream. If the stream is a SVG File and zoom is not 0,
+ * this method will try to rasterize the SVG.
+ * Throws an error if either an error occurs while loading the images, or if the images are not
+ * of a supported type. Returns the loaded image data array.
+ *
+ * @param stream the input stream to load the images from
+ * @param zoom the zoom factor to apply when rasterizing a SVG.
+ * A value of 0 means that the standard method for loading should be used.
+ * @return an array of ImageData objects loaded from the specified input stream
+ *
+ * @exception IllegalArgumentException
+ *
ERROR_NULL_ARGUMENT - if the stream is null
+ *
+ * @exception SWTException
+ *
ERROR_IO - if an IO error occurs while reading from the stream
+ *
ERROR_INVALID_IMAGE - if the image stream contains invalid data
+ *
ERROR_UNSUPPORTED_FORMAT - if the image stream contains an unrecognized format
+ *
+ *
+ * @since 3.129
+ */
+public ImageData[] load(InputStream stream, int zoom) {
+ if (stream == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ reset();
+ byte[] bytes = null;
+ try {
+ bytes = stream.readAllBytes();
+ } catch (IOException e) {
+ SWT.error(SWT.ERROR_IO, e);
+ }
+ ISVGRasterizer rasterizer = SVGRasterizerRegistry.getRasterizer();
+ if (rasterizer != null && zoom != 0) {
+ try {
+ float scalingFactor = zoom / 100.0f;
+ BufferedImage image = rasterizer.rasterizeSVG(bytes, scalingFactor);
+ if(image != null) {
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ ImageIO.write(image, "png", baos);
+ try (InputStream in = new ByteArrayInputStream(baos.toByteArray())) {
+ data = FileFormat.load(in, this);
+ return data;
+ }
+ }
+ }
+ } catch (IOException e) {
+ // try standard method
+ }
+ }
+ try (InputStream fallbackStream = new ByteArrayInputStream(bytes)) {
+ return loadDefault(fallbackStream);
+ } catch (IOException e) {
+ SWT.error(SWT.ERROR_IO, e);
+ }
+ return null;
}
/**
@@ -175,18 +240,43 @@ public ImageData[] load(InputStream stream) {
*/
public ImageData[] load(String filename) {
if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
- InputStream stream = null;
- try {
- stream = new FileInputStream(filename);
- return load(stream);
+ try (InputStream stream = new FileInputStream(filename)) {
+ return loadDefault(stream);
+ } catch (IOException e) {
+ SWT.error(SWT.ERROR_IO, e);
+ }
+ return null;
+}
+
+/**
+ * Loads an array of ImageData objects from the
+ * file with the specified name. If the filename is a SVG File and zoom is not 0,
+ * this method will try to rasterize the SVG. Throws an error if either
+ * an error occurs while loading the images, or if the images are
+ * not of a supported type. Returns the loaded image data array.
+ *
+ * @param filename the name of the file to load the images from
+ * @param zoom the zoom factor to apply when rasterizing a SVG.
+ * A value of 0 means that the standard method for loading should be used.
+ * @return an array of ImageData objects loaded from the specified file
+ *
+ * @exception IllegalArgumentException
+ *
ERROR_NULL_ARGUMENT - if the file name is null
+ *
+ * @exception SWTException
+ *
ERROR_IO - if an IO error occurs while reading from the file
+ *
ERROR_INVALID_IMAGE - if the image file contains invalid data
+ *
ERROR_UNSUPPORTED_FORMAT - if the image file contains an unrecognized format
+ *
+ *
+ * @since 3.129
+ */
+public ImageData[] load(String filename, int zoom) {
+ if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ try (InputStream stream = new FileInputStream(filename)) {
+ return load(stream, zoom);
} catch (IOException e) {
SWT.error(SWT.ERROR_IO, e);
- } finally {
- try {
- if (stream != null) stream.close();
- } catch (IOException e) {
- // Ignore error
- }
}
return null;
}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ISVGRasterizer.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ISVGRasterizer.java
new file mode 100644
index 0000000000..117a840fdf
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ISVGRasterizer.java
@@ -0,0 +1,27 @@
+package org.eclipse.swt.graphics;
+
+import java.awt.image.*;
+import java.io.*;
+
+/**
+ * Defines the interface for an SVG rasterizer, responsible for converting SVG
+ * data into rasterized images.
+ *
+ * @since 3.129
+ */
+public interface ISVGRasterizer {
+
+ /**
+ * Rasterizes an SVG image from the provided byte array, using the specified
+ * zoom factor.
+ *
+ * @param bytes the SVG image as a byte array.
+ * @param scalingFactor the scaling ratio e.g. 2.0 for doubled size.
+ * @return a {@link BufferedImage} containing the rasterized image, or
+ * {@code null} if the input is not a valid SVG file or cannot be
+ * processed.
+ * @throws IOException if an error occurs while reading the SVG data.
+ */
+ public BufferedImage rasterizeSVG(byte[] bytes, float scalingFactor) throws IOException;
+
+}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageData.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageData.java
index c268e5caae..a0d03843db 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageData.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageData.java
@@ -331,7 +331,39 @@ scanlinePad, checkData(data), 0, null,
* @see ImageLoader#load(InputStream)
*/
public ImageData(InputStream stream) {
- ImageData[] data = ImageDataLoader.load(stream);
+ this(stream, 0);
+}
+
+/**
+ * Constructs an ImageData loaded from the specified
+ * input stream. Throws an error if an error occurs while loading
+ * the image, or if the image has an unsupported type. Application
+ * code is still responsible for closing the input stream.
+ *
+ * This constructor is provided for convenience when loading a single
+ * image only. If the stream contains multiple images, only the first
+ * one will be loaded. To load multiple images, use
+ * ImageLoader.load().
+ *
+ *
+ * @param stream the input stream to load the image from (must not be null)
+ * @param zoom the zoom factor to apply when rasterizing a SVG.
+ * A value of 0 means that the standard method for loading should be used.
+ *
+ * @exception IllegalArgumentException
+ *
ERROR_NULL_ARGUMENT - if the stream is null
+ *
+ * @exception SWTException
+ *
ERROR_IO - if an IO error occurs while reading from the stream
+ *
ERROR_INVALID_IMAGE - if the image stream contains invalid data
+ *
ERROR_UNSUPPORTED_FORMAT - if the image stream contains an unrecognized format
+ *
+ *
+ * @see ImageLoader#load(InputStream)
+ * @since 3.129
+ */
+public ImageData(InputStream stream, int zoom) {
+ ImageData[] data = ImageDataLoader.load(stream, zoom);
if (data.length < 1) SWT.error(SWT.ERROR_INVALID_IMAGE);
ImageData i = data[0];
setAllFields(
@@ -377,7 +409,36 @@ public ImageData(InputStream stream) {
*
*/
public ImageData(String filename) {
- ImageData[] data = ImageDataLoader.load(filename);
+ this(filename, 0);
+}
+
+/**
+ * Constructs an ImageData loaded from a file with the
+ * specified name. Throws an error if an error occurs loading the
+ * image, or if the image has an unsupported type.
+ *
+ * This constructor is provided for convenience when loading a single
+ * image only. If the file contains multiple images, only the first
+ * one will be loaded. To load multiple images, use
+ * ImageLoader.load().
+ *
+ *
+ * @param filename the name of the file to load the image from (must not be null)
+ * @param zoom the zoom factor to apply when rasterizing a SVG.
+ * A value of 0 means that the standard method for loading should be used.
+ * @exception IllegalArgumentException
+ *
ERROR_NULL_ARGUMENT - if the file name is null
+ *
+ * @exception SWTException
+ *
ERROR_IO - if an IO error occurs while reading from the file
+ *
ERROR_INVALID_IMAGE - if the image file contains invalid data
+ *
ERROR_UNSUPPORTED_FORMAT - if the image file contains an unrecognized format
+ *
+ *
+ * @since 3.129
+ */
+public ImageData(String filename, int zoom) {
+ ImageData[] data = ImageDataLoader.load(filename, zoom);
if (data.length < 1) SWT.error(SWT.ERROR_INVALID_IMAGE);
ImageData i = data[0];
setAllFields(
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataLoader.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataLoader.java
index b1fe23d247..9c9da788bf 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataLoader.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataLoader.java
@@ -25,8 +25,16 @@ public static ImageData[] load(InputStream stream) {
return new ImageLoader().load(stream);
}
- public static ImageData[] load(String filename) {
+ public static ImageData[] load(InputStream stream, int zoom) {
+ return new ImageLoader().load(stream, zoom);
+ }
+
+ public static ImageData[] load(String filename) {
return new ImageLoader().load(filename);
}
+ public static ImageData[] load(String filename, int zoom) {
+ return new ImageLoader().load(filename, zoom);
+ }
+
}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/SVGRasterizerRegistry.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/SVGRasterizerRegistry.java
new file mode 100644
index 0000000000..d1fd380927
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/SVGRasterizerRegistry.java
@@ -0,0 +1,38 @@
+package org.eclipse.swt.graphics;
+
+/**
+ * A registry for managing the instance of an {@link ISVGRasterizer} implementation.
+ * This allows for the registration and retrieval of a single rasterizer instance.
+ *
+ * @since 3.129
+ */
+public class SVGRasterizerRegistry {
+
+ /**
+ * The instance of the registered {@link ISVGRasterizer}.
+ */
+ private static ISVGRasterizer rasterizer;
+
+ /**
+ * Registers the provided implementation of {@link ISVGRasterizer}.
+ * If a rasterizer has already been registered, subsequent calls to this method
+ * will have no effect.
+ *
+ * @param implementation the {@link ISVGRasterizer} implementation to register.
+ */
+ public static void register(ISVGRasterizer implementation) {
+ if (rasterizer == null) {
+ rasterizer = implementation;
+ }
+ }
+
+ /**
+ * Retrieves the currently registered {@link ISVGRasterizer} implementation.
+ *
+ * @return the registered {@link ISVGRasterizer}, or {@code null} if no implementation
+ * has been registered.
+ */
+ public static ISVGRasterizer getRasterizer() {
+ return rasterizer;
+ }
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/SVGUtil.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/SVGUtil.java
new file mode 100644
index 0000000000..939e475200
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/SVGUtil.java
@@ -0,0 +1,43 @@
+package org.eclipse.swt.graphics;
+
+import java.io.*;
+import java.nio.charset.*;
+
+/**
+ * Utility class for handling SVG-related operations.
+ *
+ * @since 3.129
+ */
+public class SVGUtil {
+
+ /**
+ * Determines whether the given {@link InputStream} contains a SVG file.
+ *
+ * @param data byte array to check.
+ * @return {@code true} if the input stream contains SVG content; {@code false}
+ * otherwise.
+ * @throws IOException if an error occurs while reading the stream.
+ * @throws IllegalArgumentException if the input stream is {@code null}.
+ */
+ public static boolean isSVGFile(byte[] data) throws IOException {
+ String content = new String(data, 0, Math.min(data.length, 512), StandardCharsets.UTF_8);
+ return content.contains("