https://github.com/weisj/jsvg
Java SVG renderer
https://github.com/weisj/jsvg
svg
Last synced: 4 days ago
JSON representation
Java SVG renderer
- Host: GitHub
- URL: https://github.com/weisj/jsvg
- Owner: weisJ
- License: mit
- Created: 2021-09-07T21:34:48.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2025-04-24T12:16:46.000Z (26 days ago)
- Last Synced: 2025-05-16T13:07:28.679Z (4 days ago)
- Topics: svg
- Language: Java
- Homepage:
- Size: 2.77 MB
- Stars: 165
- Watchers: 3
- Forks: 11
- Open Issues: 30
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
[](https://sonarcloud.io/summary/new_code?id=com.github.weisj%3Ajsvg)
[](https://github.com/weisJ/jsvg/actions/workflows/spotless.yml)
[](https://github.com/weisJ/jsvg/actions/workflows/gradle.yml)
[](https://search.maven.org/artifact/com.github.weisj/jsvg)[](https://www.buymeacoffee.com/weisj)
# JSVG - A Java SVG implementation
![]()
The SVG logo rendered using JSVGJSVG is an SVG user agent using AWT graphics. Its aim is to provide a small and fast implementation.
This library is under active development and doesn't yet support all features of the SVG specification
(see [Supported features](#supported-features)). However it does already cover most use cases and already supports more
features than [svgSalamander](https://github.com/blackears/svgSalamander).
This implementation only tries to be a static user agent meaning it won't support any scripting languages or interaction.
Partial animations exists and will be extended in future versions.This library aims to be as lightweight as possible. Generally JSVG uses ~50% less memory than svgSalamander and
~98% less than Batik.## Projects using JSVG
* The [Jetbrains IDEA IDE](https://github.com/JetBrains/intellij-community) suite ([YouTrack Ticket](https://youtrack.jetbrains.com/issue/IJPL-81/Switch-to-JSVG-for-SVG-icon-rendering?reloaded=true)).
* [Apache NetBeans](https://netbeans.apache.org/front/main/index.html) ([PR #7941](https://github.com/apache/netbeans/pull/7941))
* [Eclipse SWT](https://github.com/eclipse-platform/eclipse.platform.swt) ([PR #1638](https://github.com/eclipse-platform/eclipse.platform.swt/pull/1638))
* [FlatLaf](https://github.com/JFormDesigner/FlatLaf) for [FlatSVGIcon](https://github.com/JFormDesigner/FlatLaf/tree/main/flatlaf-extras) ([PR #684](https://github.com/JFormDesigner/FlatLaf/pull/684))## How to use
The library is available on maven central:
````kotlin
dependencies {
implementation("com.github.weisj:jsvg:2.0.0")
}
````Also, nightly snapshot builds will be released to maven:
````kotlin
repositories {
maven {
url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
}
}// Optional:
configurations.all {
resolutionStrategy.cacheChangingModulesFor(0, "seconds")
}dependencies {
implementation("com.github.weisj:jsvg:latest.integration")
}
````
JSVG provides OSGi metadata in the manifest file.### Loading
To load an svg icon you can use
the [`SVGLoader`](https://github.com/weisJ/jsvg/blob/master/jsvg/src/main/java/com/github/weisj/jsvg/parser/SVGLoader.java)
class. It will produce
an [`SVGDocument`](https://github.com/weisJ/jsvg/blob/master/jsvg/src/main/java/com/github/weisj/jsvg/SVGDocument.java)````java
SVGLoader loader = new SVGLoader();
URL svgUrl = MyClass.class.getResource("mySvgFile.svg");
SVGDocument svgDocument = loader.load(svgUrl);
````If you need more control over the loading process you can pass a `LoaderContext` for configuration purposes.
````java
SVGDocument svgDocument = loader.load(svgUrl,
LoaderContext.builder()
// configure the context
// ...
.build());
````Note that `SVGLoader` is not guaranteed to be thread safe, hence shouldn't be used across multiple threads.
Note that by default XML entities will not be replaced during parsing. If you need this behaviour you can use a
custom XML parser by implementing the `XMLInput` interface. A usage example can be found
[below in the examples](#using-a-custom-xml-parser).### Rendering
An `SVGDocument` can be rendered to any `Graphics2D` object you like e.g. a `BufferedImage`
````java
FloatSize size = svgDocument.size();
BufferedImage image = new BufferedImage((int) size.width,(int) size.height);
Graphics2D g = image.createGraphics();
svgDocument.render(null,g);
g.dispose();
````or a swing component
````java
class MyComponent extends JComponent {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
svgDocument.render(this, (Graphics2D) g, new ViewBox(0, 0, getWidth(), getHeight()));
}
}
````For more in-depth examples see [Usage](#usage) examples below.
#### Rendering Quality
The rendering quality can be adjusted by setting the `RenderingHints` of the `Graphics2D` object. The following
properties are recommended:````java
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
````If either of these values are not set or have their respective default values (`VALUE_ANTIALIAS_DEFAULT` and `VALUE_STROKE_DEFAULT`)
JSVG will automatically set them to the recommended values above.JSVG also supports custom SVG specific rendering hints. These can be set using the `SVGRenderingHints` class. For example:
````java
// Will use the value of RenderingHints.KEY_ANTIALIASING by default
g.setRenderingHint(SVGRenderingHints.KEY_IMAGE_ANTIALIASING, SVGRenderingHints.VALUE_IMAGE_ANTIALIASING_ON);
````By default clipping with a `` element does not use soft-clipping (i.e. anti-aliasing along the edges of the clip shape).
This can be enabled by setting````java
g.setRenderingHint(SVGRenderingHints.KEY_SOFT_CLIPPING, SVGRenderingHints.VALUE_SOFT_CLIPPING_ON);
````In the future this will get stabilized and be enabled by default.
Supported custom rendering hints are:
| Key | Values | Default | Description |
|-----------------------------|-----------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `KEY_IMAGE_ANTIALIASING` | `VALUE_IMAGE_ANTIALIAS_ON`
`VALUE_IMAGE_ANTIALIAS_OFF` | Value of `RenderingHints.KEY_ANTIALIASING` | Enables anti-aliasing for images |
| `KEY_SOFT_CLIPPING` | `VALUE_SOFT_CLIPPING_ON`
`VALUE_SOFT_CLIPPING_OFF` | `VALUE_SOFT_CLIPPING_OFF` | Enables soft (anti-aliased) clipping for `clipPath` |
| `KEY_MASK_CLIP_RENDERING` | `VALUE_MASK_CLIP_RENDERING_FAST`
`VALUE_MASK_CLIP_RENDERING_ACCURACY`
`VALUE_MASK_CLIP_RENDERING_DEFAULT` | `VALUE_MASK_CLIP_RENDERING_DEFAULT = VALUE_MASK_CLIP_RENDERING_FAST` | Changes how masks and clip paths are rendered. Accurate rendering enforces the sub-image to which the mask/clip is applied to be rendered on its own isolated offscreen image |
| `KEY_CACHE_OFFSCREEN_IMAGE` | `VALUE_USE_CACHE`
`VALUE_NO_CACHE` | `VALUE_USE_CACHE` | Whether to cache offscreen images. This can be useful for performance reasons, but can also lead to increased memory usage. |All are exposed through the `SVGRenderingHints`class.
### Animations
The current support for animations is limited and in an experimental state.
Only basic timing mechanisms and interpolation methods are supported.
Moreover most animatable properties aren't yet supported.
Please beware that the API for animations is subject to change.Animations can be controlled on a per frame basis by supplying an `AnimationState` to `SVGDocument#renderWithPlatform`.
In particular this means that animations need to be driven by the user code.
See below for examples on how to do this.## Supported features
For supported elements most of the attributes which apply to them are implemented.
- :white_check_mark:: The element is supported. Note that this doesn't mean that every attribute is supported.
- :white_check_mark:*: The element is supported, but won't have any effect (e.g. it's currently not possible to query
the content of a `` element)
- :ballot_box_with_check:: The element is partially implemented and might not support most basic features of the
element.
- :x:: The element is currently not supported
- :warning:: The element is deprecated in the spec and has a low priority of getting implemented.
- :test_tube:: The element is an experimental part of the svg 2.* spec. It may not fully behave as expected.### Shape and container elements
| Element | Status |
|---------------|---------------------|
| a | :white_check_mark: |
| circle | :white_check_mark: |
| clipPath | :white_check_mark: |
| defs | :white_check_mark: |
| ellipse | :white_check_mark: |
| foreignObject | :x: |
| g | :white_check_mark: |
| image | :white_check_mark: |
| line | :white_check_mark: |
| marker | :white_check_mark: |
| mask | :white_check_mark: |
| path | :white_check_mark: |
| polygon | :white_check_mark: |
| polyline | :white_check_mark: |
| rect | :white_check_mark: |
| svg | :white_check_mark: |
| symbol | :white_check_mark: |
| use | :white_check_mark: |
| view | :white_check_mark:* |### Paint server elements
| Element | Status |
|-------------------------|--------------------|
| linearGradient | :white_check_mark: |
| :test_tube:meshgradient | :white_check_mark: |
| :test_tube:meshrow | :white_check_mark: |
| :test_tube:meshpatch | :white_check_mark: |
| pattern | :white_check_mark: |
| radialGradient | :white_check_mark: |
| solidColor | :white_check_mark: |
| stop | :white_check_mark: |### Text elements
| Element | Status |
|---------------|--------------------|
| text | :white_check_mark: |
| textPath | :white_check_mark: |
| :warning:tref | :x: |
| tspan | :white_check_mark: |### Animation elements
| Element | Status |
|-----------------------|-------------------------|
| animate | :ballot_box_with_check: |
| :warning:animateColor | :x: |
| animateMotion | :x: |
| animateTransform | :ballot_box_with_check: |
| mpath | :x: |
| set | :x: |
| switch | :x: |### Filter elements
| Element | Status |
|---------------------|-------------------------|
| feBlend | :white_check_mark: |
| feColorMatrix | :white_check_mark: |
| feComponentTransfer | :white_check_mark: |
| feComposite | :white_check_mark: |
| feConvolveMatrix | :x: |
| feDiffuseLighting | :white_check_mark: |
| feDisplacementMap | :white_check_mark: |
| feDistantLight | :x: |
| feDropShadow | :white_check_mark: |
| feFlood | :white_check_mark: |
| feFuncA | :white_check_mark: |
| feFuncB | :white_check_mark: |
| feFuncG | :white_check_mark: |
| feFuncR | :white_check_mark: |
| feGaussianBlur | :white_check_mark: |
| feImage | :x: |
| feMerge | :white_check_mark: |
| feMergeNode | :white_check_mark: |
| feMorphology | :x: |
| feOffset | :white_check_mark: |
| fePointLight | :x: |
| feSpecularLighting | :x: |
| feSpotLight | :x: |
| feTile | :x: |
| feTurbulence | :white_check_mark: |
| filter | :ballot_box_with_check: |### Font elements
| Element | Status |
|---------------------------|--------|
| :warning:altGlyph | :x: |
| :warning:altGlyphDef | :x: |
| :warning:altGlyphItem | :x: |
| :warning:font | :x: |
| :warning:font-face | :x: |
| :warning:font-face-format | :x: |
| :warning:font-face-name | :x: |
| :warning:font-face-src | :x: |
| :warning:font-face-uri | :x: |
| :warning:glyph | :x: |
| :warning:glyphRef | :x: |
| :warning:hkern | :x: |
| :warning:missing-glyph | :x: |
| :warning:vkern | :x: |### Other elements
| Element | Status |
|-----------------|-------------------------|
| desc | ( :white_check_mark: ) |
| title | ( :white_check_mark: ) |
| metadata | ( :white_check_mark: ) |
| color-profile | :x: |
| :warning:cursor | :x: |
| script | :x: |
| style | :ballot_box_with_check: |## Usage examples
### Basic
To render an SVG to a swing component you can start from the following example:````java
import javax.swing.*;
import java.awt.*;
import java.net.URL;import com.github.weisj.jsvg.*;
import com.github.weisj.jsvg.attributes.*;
import com.github.weisj.jsvg.parser.*;
import org.jetbrains.annotations.NotNull;import java.util.Objects;
public class RenderExample {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
SVGLoader loader = new SVGLoader();URL svgUrl = RenderExample.class.getResource("path/to/image.svg");
SVGDocument document = loader.load(Objects.requireNonNull(svgUrl, "SVG file not found"));JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setPreferredSize(new Dimension(400, 400));
frame.setContentPane(new SVGPanel(Objects.requireNonNull(document)));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}static class SVGPanel extends JPanel {
private @NotNull final SVGDocument document;SVGPanel(@NotNull SVGDocument document) {
this.document = document;
}@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
((Graphics2D) g).setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
((Graphics2D) g).setRenderingHint(
RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);document.render(this, (Graphics2D) g, new ViewBox(0, 0, getWidth(), getHeight()));
}
}
}
````### DOM manipulation
You can even change the color of svg elements by using a suitable `DomProcessor` together with a custom implementation
of `SVGPaint`. Lets take the following SVG as an example:````svg
````
We want to change the color if the first rectangle at runtime. We start by loading the SVG using a custom `ParserProvider`
which returns a `DomProcessor`for the pre-processing step. The `DomProcessor` will allow us to change attributes
of the SVG elements before they are fully parsed.````java
CustomColorsProcessor processor = new CustomColorsProcessor(List.of("myRect"));
document = loader.load(svgUrl, LoaderContext.builder().preProcessor(processor).build());
````The heavy lifting is done by the `CustomColorsProcessor` class which looks like this:
````java
class CustomColorsProcessor implements DomProcessor {private final Map customColors = new HashMap<>();
public CustomColorsProcessor(@NotNull List elementIds) {
for (String elementId : elementIds) {
customColors.put(elementId, new DynamicAWTSvgPaint(Color.BLACK));
}
}@Nullable DynamicAWTSvgPaint customColorForId(@NotNull String id) {
return customColors.get(id);
}@Override
public void process(@NotNull DomElement root) {
processImpl(root);
root.children().forEach(this::process);
}private void processImpl(@NotNull DomElement element) {
// Obtain the id of the element
// Note: There that Element also has a node() method to obtain the SVGNode. However during the pre-processing
// phase the SVGNode is not yet fully parsed and doesn't contain any non-defaulted information.
String nodeId = element.id();// Check if this element is one of the elements we want to change the color of
if (customColors.containsKey(nodeId)) {
DynamicAWTSvgPaint dynamicColor = customColors.get(nodeId);// This assumed that the fill attribute is a color and not a gradient or pattern.
Color color = element.document().loaderContext().paintParser()
.parseColor(element.attribute("fill", "black"));
if (color == null) color = Color.BLACK;dynamicColor.setColor(color);
// This can be anything as long as it's unique
String uniqueIdForDynamicColor = UUID.randomUUID().toString();// Register the dynamic color as a custom element
element.document().registerNamedElement(uniqueIdForDynamicColor, dynamicColor);// Refer to the custom element as the fill attribute
element.setAttribute("fill", uniqueIdForDynamicColor);// Note: This class can easily be adapted to also support changing the stroke color.
// With a bit more work it could also support changing the color of gradients and patterns.
}
}
}class DynamicAWTSvgPaint implements SimplePaintSVGPaint {
private @NotNull Color color;
DynamicAWTSvgPaint(@NotNull Color color) {
this.color = color;
}public void setColor(@NotNull Color color) {
this.color = color;
}public @NotNull Color color() {
return color;
}@Override
public @NotNull Paint paint() {
return color;
}
}
````Now we simply have to obtain the `DynamicAWTSvgPaint` instance for the element we want to change the color of and
hook it up in our UI:````java
DynamicAWTSvgPaint dynamicColor = processor.customColorForId("myRect");SVGPanel panel = new SVGPanel(document);
JButton button = new JButton("Change color");
button.addActionListener(e -> {
Color newColor = JColorChooser.showDialog(panel, "Choose a color", dynamicColor.color());
if (newColor != null) {
dynamicColor.setColor(newColor);
// Make sure to repaint the panel to see the changes
panel.repaint();
}
});
JPanel content = new JPanel(new BorderLayout());
content.add(panel, BorderLayout.CENTER);
content.add(button, BorderLayout.SOUTH);
frame.setContentPane(content);
````
### AnimationsJSVG provides a helper class `AnimationPlayer` for implementing animations in Swing components.
The following example demonstrates how to use the `AnimationPlayer` to animate an SVG element:````java
import javax.swing.*;public class AnimationPanel extends JComponent {
private final @NotNull SVGDocument document;
private final @NotNull AnimationPlayer player;public AnimationPanel(@NotNull SVGDocument document) {
this.document = document;
this.player = new AnimationPlayer(e -> repaint());
}@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Setup rendering hints (see above)
// ...
document.renderWithPlatform(
new AwtComponentPlatformSupport(this),
Output.createForGraphics((Graphics2D) g),
new ViewBox(0, 0, getWidth(), getHeight()),
animationPlayer.animationState());
}public void startAnimation() {
player.start();
}public void stopAnimation() {
player.stop();
}
}
````
### Using a custom XML parserIf you need more control over how the XML source is parsed you can e.g. use a custom `XMLInputFactory`.
````java
public class CustomXMLInput implements XMLInput {
private final @NotNull XMLInputFactory factory;
private final @NotNull InputStream inputStream;private CustomXMLInput(@NotNull XMLInputFactory factory, @NotNull InputStream inputStream) {
this.factory = factory;
this.inputStream = inputStream;
}@Override
public @NotNull XMLEventReader createReader() throws XMLStreamException {
return factory.createXMLEventReader(inputStream);
}
}XMLInpitFactory factory = XMLInputFactory.newFactory();
// Set up the factory to your liking
URL inputUrl = ...;
SVGLoader loader = new SVGLoader();
try (InputStream inputStream = inputUrl.openStream()){
SVGDocument document = loader.load(
new CustomXMLInput(factory,inputStream),
inputUrl,
LoaderContext.createDefault()
);
}````