Repository files navigation

Quality Gate StatusCode StyleCIMaven Central

"Buy Me A Coffee"

JSVG - A Java SVG implementation

The SVG logo rendered by JSVG
The SVG logo rendered using JSVG

JSVG 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). However it does already cover most use cases and already supports more features than 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.

Table of contents

Projects using JSVG

How to use

The library is available on maven central:

dependencies {
implementation("com.github.weisj:jsvg:2.1.0")
}

Also, nightly snapshot builds will be released to maven:

repositories {
maven {
url = uri("https://central.sonatype.com/repository/maven-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 class. It will produce an SVGDocument

SVGLoaderloader = newSVGLoader();
URLsvgUrl = MyClass.class.getResource("mySvgFile.svg");
SVGDocumentsvgDocument = loader.load(svgUrl);

If you need more control over the loading process you can pass a LoaderContext for configuration purposes.

SVGDocumentsvgDocument = 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.

Rendering

An SVGDocument can be rendered to any Graphics2D object you like e.g. a BufferedImage

FloatSizesize = svgDocument.size();
BufferedImageimage = newBufferedImage((int) size.width,(int) size.height);
Graphics2Dg = image.createGraphics();
svgDocument.render(null,g);
g.dispose();

or a swing component

classMyComponentextendsJComponent {
@OverrideprotectedvoidpaintComponent(Graphicsg) {
super.paintComponent(g);
svgDocument.render(this, (Graphics2D) g, newViewBox(0, 0, getWidth(), getHeight()));
}
}

For more in-depth examples see Usage examples below.

Rendering Quality

The rendering quality can be adjusted by setting the RenderingHints of the Graphics2D object. The following properties are recommended:

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:

// Will use the value of RenderingHints.KEY_ANTIALIASING by defaultg.setRenderingHint(SVGRenderingHints.KEY_IMAGE_ANTIALIASING, SVGRenderingHints.VALUE_IMAGE_ANTIALIASING_ON);

By default clipping with a <clipPath> element does not use soft-clipping (i.e. anti-aliasing along the edges of the clip shape). This can be enabled by setting

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:

KeyValuesDefaultDescription
KEY_IMAGE_ANTIALIASINGVALUE_IMAGE_ANTIALIAS_ON
VALUE_IMAGE_ANTIALIAS_OFF
Value of RenderingHints.KEY_ANTIALIASINGEnables anti-aliasing for images
KEY_SOFT_CLIPPINGVALUE_SOFT_CLIPPING_ON
VALUE_SOFT_CLIPPING_OFF
VALUE_SOFT_CLIPPING_OFFEnables soft (anti-aliased) clipping for clipPath
KEY_MASK_CLIP_RENDERINGVALUE_MASK_CLIP_RENDERING_FAST
VALUE_MASK_CLIP_RENDERING_ACCURACY
VALUE_MASK_CLIP_RENDERING_DEFAULT
VALUE_MASK_CLIP_RENDERING_DEFAULT = VALUE_MASK_CLIP_RENDERING_FASTChanges 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_IMAGEVALUE_USE_CACHE
VALUE_NO_CACHE
VALUE_USE_CACHEWhether 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 the Animations (Swing) and JavaFX usage examples below for details.

Additional modules

JavaFX renderer (experimental)

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

JSVG provides an optional JavaFX rendering module that allows SVG documents to be displayed inside a JavaFX application. It requires JavaFX 17 or later.

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-javafx:2.0.1")
}

See the JavaFX usage example for a full code sample.

Logging

By default JSVG uses java.util.logging (JUL) for internal diagnostics. Two optional adapter modules are provided so you can route JSVG log output through your own logging framework without any additional configuration code — simply add the desired module to the classpath/module-path and the adapter is picked up automatically via ServiceLoader.

SLF4J adapter

Routes JSVG log output through any SLF4J 2.x compatible backend (Logback, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-slf4j:2.0.1")
// also add your preferred SLF4J backend, e.g.:
runtimeOnly("ch.qos.logback:logback-classic:1.5.6")
}
System.Logger adapter

Routes JSVG log output through the Java 9+ System.Logger API, which in turn delegates to whatever logging backend has been installed for the JVM (JUL, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-systemlogger:2.0.1")
}

Both adapters provide OSGi metadata and register themselves as LogManager service providers. Only one adapter should be present on the classpath at a time.

Supported features

For supported elements most of the attributes which apply to them are implemented.

  • ✅: The element is supported. Note that this doesn't mean that every attribute is supported.
  • ✅*: The element is supported, but won't have any effect (e.g. it's currently not possible to query the content of a <desc> element)
  • ☑️: The element is partially implemented and might not support most basic features of the element.
  • ❌: The element is currently not supported
  • ⚠️: The element is deprecated in the spec and has a low priority of getting implemented.
  • 🧪: The element is an experimental part of the svg 2.* spec. It may not fully behave as expected.

Shape and container elements

ElementStatus
a
circle
clipPath
defs
ellipse
foreignObject
g
image
line
marker
mask
path
polygon
polyline
rect
svg
symbol
use
view✅*

Paint server elements

ElementStatus
linearGradient
🧪meshgradient
🧪meshrow
🧪meshpatch
pattern
radialGradient
solidColor
stop

Text elements

ElementStatus
text
textPath
⚠️tref
tspan

Animation elements

ElementStatus
animate☑️
⚠️animateColor
animateMotion
animateTransform☑️
mpath
set
switch

Filter elements

ElementStatus
feBlend
feColorMatrix
feComponentTransfer
feComposite
feConvolveMatrix
feDiffuseLighting
feDisplacementMap
feDistantLight
feDropShadow
feFlood
feFuncA
feFuncB
feFuncG
feFuncR
feGaussianBlur
feImage
feMerge
feMergeNode
feMorphology
feOffset
fePointLight
feSpecularLighting
feSpotLight
feTile
feTurbulence
filter☑️

Font elements

ElementStatus
⚠️altGlyph
⚠️altGlyphDef
⚠️altGlyphItem
⚠️font
⚠️font-face
⚠️font-face-format
⚠️font-face-name
⚠️font-face-src
⚠️font-face-uri
⚠️glyph
⚠️glyphRef
⚠️hkern
⚠️missing-glyph
⚠️vkern

Other elements

ElementStatus
desc( ✅ )
title( ✅ )
metadata( ✅ )
color-profile
⚠️cursor
script
style☑️

Usage examples

Basic (Swing)

To render an SVG to a Swing component you can start from the following example:

importjavax.swing.*;
importjava.awt.*;
importjava.net.URL;
importjava.util.Objects;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassRenderExample {
publicstaticvoidmain(String[] args) {
SwingUtilities.invokeLater(() -> {
SVGLoaderloader = newSVGLoader();
URLsvgUrl = RenderExample.class.getResource("path/to/image.svg");
SVGDocumentdocument = loader.load(Objects.requireNonNull(svgUrl, "SVG file not found"));
JFrameframe = newJFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setPreferredSize(newDimension(400, 400));
frame.setContentPane(newSVGPanel(document));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
staticclassSVGPanelextendsJPanel {
privatefinal@NotNullSVGDocumentdocument;
SVGPanel(@NotNullSVGDocumentdocument) {
this.document = document;
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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, newViewBox(0, 0, getWidth(), getHeight()));
}
}
}

JavaFX

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

Required dependency: com.github.weisj:jsvg-javafx:2.0.1 (JavaFX 17 or later). See JavaFX renderer for the full dependency declaration.

The main entry point is FXSVGCanvas, a standard JavaFX Control that can be placed anywhere in a scene graph:

importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.ui.jfx.FXSVGCanvas;
importjavafx.application.Application;
importjavafx.scene.Scene;
importjavafx.scene.layout.StackPane;
importjavafx.stage.Stage;
publicclassFXRenderExampleextendsApplication {
@Overridepublicvoidstart(Stagestage) {
SVGLoaderloader = newSVGLoader();
SVGDocumentdocument = loader.load(getClass().getResource("path/to/image.svg"));
FXSVGCanvascanvas = newFXSVGCanvas();
// Choose the rendering backend:// RenderBackend.JavaFX - renders directly to a GraphicsContext (faster, hardware accelerated,// but some advanced features such as filters and masks may not render correctly)// RenderBackend.AWT - renders via the JSVG AWT pipeline (slower, but more accurate)canvas.setRenderBackend(FXSVGCanvas.RenderBackend.JavaFX);
canvas.setDocument(document);
stage.setScene(newScene(newStackPane(canvas), 400, 300));
stage.show();
}
publicstaticvoidmain(String[] args) {
launch(args);
}
}

FXSVGCanvas exposes JavaFX properties so it integrates naturally with bindings:

// Bind the document property to an external observablecanvas.documentProperty().bind(currentDocumentProperty);
// Show or hide the transparency checker-board pattern behind the SVGcanvas.setShowTransparentPattern(true);
// Places the svg viewport inside this region within the SVG canvas.canvas.setViewBox(newViewBox(0, 0, 200, 200));

Animations are driven automatically when animated is true (the default). You can also control playback manually:

canvas.pauseAnimation();
canvas.playAnimation();
canvas.restartAnimation();
// Disable automatic animation entirelycanvas.setAnimated(false);

For a more complete working example see FXTestViewerApplication in the test sources.

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:

<svgxmlns="http://www.w3.org/2000/svg"width="100"height="100"viewBox="0 0 100 100">
<rectx="0"y="0"width="100%"height="40%"id="myRect"></rect>
<rectx="0"y="60"width="100%"height="40%"></rect>
</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.

CustomColorsProcessorprocessor = newCustomColorsProcessor(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:

classCustomColorsProcessorimplementsDomProcessor {
privatefinalMap<String, DynamicAWTSvgPaint> customColors = newHashMap<>();
publicCustomColorsProcessor(@NotNullList<String> elementIds) {
for (StringelementId : elementIds) {
customColors.put(elementId, newDynamicAWTSvgPaint(Color.BLACK));
}
}
@NullableDynamicAWTSvgPaintcustomColorForId(@NotNullStringid) {
returncustomColors.get(id);
}
@Overridepublicvoidprocess(@NotNullDomElementroot) {
processImpl(root);
root.children().forEach(this::process);
}
privatevoidprocessImpl(@NotNullDomElementelement) {
// Obtain the id of the element.// Note: 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.StringnodeId = element.id();
if (customColors.containsKey(nodeId)) {
DynamicAWTSvgPaintdynamicColor = customColors.get(nodeId);
// This assumes the fill attribute is a plain color, not a gradient or pattern.Colorcolor = element.document().loaderContext().paintParser()
.parseColor(element.attribute("fill", "black"));
if (color == null) color = Color.BLACK;
dynamicColor.setColor(color);
// The id must be unique.StringuniqueIdForDynamicColor = UUID.randomUUID().toString();
// Register the dynamic color as a custom elementelement.document().registerNamedElement(uniqueIdForDynamicColor, dynamicColor);
// Refer to the custom element as the fill attributeelement.setAttribute("fill", uniqueIdForDynamicColor);
}
}
}
classDynamicAWTSvgPaintimplementsSimplePaintSVGPaint {
private@NotNullColorcolor;
DynamicAWTSvgPaint(@NotNullColorcolor) {
this.color = color;
}
publicvoidsetColor(@NotNullColorcolor) {
this.color = color;
}
public@NotNullColorcolor() {
returncolor;
}
@Overridepublic@NotNullPaintpaint() {
returncolor;
}
}

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:

DynamicAWTSvgPaintdynamicColor = processor.customColorForId("myRect");
SVGPanelpanel = newSVGPanel(document);
JButtonbutton = newJButton("Change color");
button.addActionListener(e -> {
ColornewColor = JColorChooser.showDialog(panel, "Choose a color", dynamicColor.color());
if (newColor != null) {
dynamicColor.setColor(newColor);
// Make sure to repaint the panel to see the changespanel.repaint();
}
});
JPanelcontent = newJPanel(newBorderLayout());
content.add(panel, BorderLayout.CENTER);
content.add(button, BorderLayout.SOUTH);
frame.setContentPane(content);

Animations (Swing)

JSVG provides a helper class AnimationPlayer for implementing animations in Swing components. The following example demonstrates how to use the AnimationPlayer to animate an SVG document:

importjavax.swing.*;
importjava.awt.*;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.renderer.animation.AnimationState;
importcom.github.weisj.jsvg.ui.AnimationPlayer;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassAnimationPanelextendsJComponent {
privatefinal@NotNullSVGDocumentdocument;
privatefinal@NotNullAnimationPlayerplayer;
publicAnimationPanel(@NotNullSVGDocumentdocument) {
this.document = document;
this.player = newAnimationPlayer(e -> repaint());
player.setAnimation(document.animation());
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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.renderWithPlatform(
newAwtComponentPlatformSupport(this),
Output.createForGraphics((Graphics2D) g),
newViewBox(0, 0, getWidth(), getHeight()),
player.animationState());
}
publicvoidstartAnimation() {
player.start();
}
publicvoidstopAnimation() {
player.stop();
}
}

Using a custom XML parser

If you need more control over how the XML source is parsed you can e.g. use a custom XMLInputFactory.

publicclassCustomXMLInputimplementsXMLInput {
privatefinal@NotNullXMLInputFactoryfactory;
privatefinal@NotNullInputStreaminputStream;
privateCustomXMLInput(@NotNullXMLInputFactoryfactory, @NotNullInputStreaminputStream) {
this.factory = factory;
this.inputStream = inputStream;
}
@Overridepublic@NotNullXMLEventReadercreateReader() throwsXMLStreamException {
returnfactory.createXMLEventReader(inputStream);
}
}
XMLInputFactoryfactory = XMLInputFactory.newFactory();
// Set up the factory to your likingURLinputUrl = ...;
SVGLoaderloader = newSVGLoader();
try (InputStreaminputStream = inputUrl.openStream()) {
SVGDocumentdocument = loader.load(
newCustomXMLInput(factory, inputStream),
inputUrl,
LoaderContext.createDefault()
);
}

About

Java SVG renderer

Topics

Resources

Contributing

Stars

221 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Quality Gate StatusCode StyleCIMaven Central

"Buy Me A Coffee"

JSVG - A Java SVG implementation

The SVG logo rendered by JSVG
The SVG logo rendered using JSVG

JSVG 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). However it does already cover most use cases and already supports more features than 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.

Table of contents

Projects using JSVG

How to use

The library is available on maven central:

dependencies {
implementation("com.github.weisj:jsvg:2.1.0")
}

Also, nightly snapshot builds will be released to maven:

repositories {
maven {
url = uri("https://central.sonatype.com/repository/maven-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 class. It will produce an SVGDocument

SVGLoaderloader = newSVGLoader();
URLsvgUrl = MyClass.class.getResource("mySvgFile.svg");
SVGDocumentsvgDocument = loader.load(svgUrl);

If you need more control over the loading process you can pass a LoaderContext for configuration purposes.

SVGDocumentsvgDocument = 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.

Rendering

An SVGDocument can be rendered to any Graphics2D object you like e.g. a BufferedImage

FloatSizesize = svgDocument.size();
BufferedImageimage = newBufferedImage((int) size.width,(int) size.height);
Graphics2Dg = image.createGraphics();
svgDocument.render(null,g);
g.dispose();

or a swing component

classMyComponentextendsJComponent {
@OverrideprotectedvoidpaintComponent(Graphicsg) {
super.paintComponent(g);
svgDocument.render(this, (Graphics2D) g, newViewBox(0, 0, getWidth(), getHeight()));
}
}

For more in-depth examples see Usage examples below.

Rendering Quality

The rendering quality can be adjusted by setting the RenderingHints of the Graphics2D object. The following properties are recommended:

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:

// Will use the value of RenderingHints.KEY_ANTIALIASING by defaultg.setRenderingHint(SVGRenderingHints.KEY_IMAGE_ANTIALIASING, SVGRenderingHints.VALUE_IMAGE_ANTIALIASING_ON);

By default clipping with a <clipPath> element does not use soft-clipping (i.e. anti-aliasing along the edges of the clip shape). This can be enabled by setting

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:

KeyValuesDefaultDescription
KEY_IMAGE_ANTIALIASINGVALUE_IMAGE_ANTIALIAS_ON
VALUE_IMAGE_ANTIALIAS_OFF
Value of RenderingHints.KEY_ANTIALIASINGEnables anti-aliasing for images
KEY_SOFT_CLIPPINGVALUE_SOFT_CLIPPING_ON
VALUE_SOFT_CLIPPING_OFF
VALUE_SOFT_CLIPPING_OFFEnables soft (anti-aliased) clipping for clipPath
KEY_MASK_CLIP_RENDERINGVALUE_MASK_CLIP_RENDERING_FAST
VALUE_MASK_CLIP_RENDERING_ACCURACY
VALUE_MASK_CLIP_RENDERING_DEFAULT
VALUE_MASK_CLIP_RENDERING_DEFAULT = VALUE_MASK_CLIP_RENDERING_FASTChanges 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_IMAGEVALUE_USE_CACHE
VALUE_NO_CACHE
VALUE_USE_CACHEWhether 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 the Animations (Swing) and JavaFX usage examples below for details.

Additional modules

JavaFX renderer (experimental)

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

JSVG provides an optional JavaFX rendering module that allows SVG documents to be displayed inside a JavaFX application. It requires JavaFX 17 or later.

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-javafx:2.0.1")
}

See the JavaFX usage example for a full code sample.

Logging

By default JSVG uses java.util.logging (JUL) for internal diagnostics. Two optional adapter modules are provided so you can route JSVG log output through your own logging framework without any additional configuration code — simply add the desired module to the classpath/module-path and the adapter is picked up automatically via ServiceLoader.

SLF4J adapter

Routes JSVG log output through any SLF4J 2.x compatible backend (Logback, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-slf4j:2.0.1")
// also add your preferred SLF4J backend, e.g.:
runtimeOnly("ch.qos.logback:logback-classic:1.5.6")
}
System.Logger adapter

Routes JSVG log output through the Java 9+ System.Logger API, which in turn delegates to whatever logging backend has been installed for the JVM (JUL, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-systemlogger:2.0.1")
}

Both adapters provide OSGi metadata and register themselves as LogManager service providers. Only one adapter should be present on the classpath at a time.

Supported features

For supported elements most of the attributes which apply to them are implemented.

  • ✅: The element is supported. Note that this doesn't mean that every attribute is supported.
  • ✅*: The element is supported, but won't have any effect (e.g. it's currently not possible to query the content of a <desc> element)
  • ☑️: The element is partially implemented and might not support most basic features of the element.
  • ❌: The element is currently not supported
  • ⚠️: The element is deprecated in the spec and has a low priority of getting implemented.
  • 🧪: The element is an experimental part of the svg 2.* spec. It may not fully behave as expected.

Shape and container elements

ElementStatus
a
circle
clipPath
defs
ellipse
foreignObject
g
image
line
marker
mask
path
polygon
polyline
rect
svg
symbol
use
view✅*

Paint server elements

ElementStatus
linearGradient
🧪meshgradient
🧪meshrow
🧪meshpatch
pattern
radialGradient
solidColor
stop

Text elements

ElementStatus
text
textPath
⚠️tref
tspan

Animation elements

ElementStatus
animate☑️
⚠️animateColor
animateMotion
animateTransform☑️
mpath
set
switch

Filter elements

ElementStatus
feBlend
feColorMatrix
feComponentTransfer
feComposite
feConvolveMatrix
feDiffuseLighting
feDisplacementMap
feDistantLight
feDropShadow
feFlood
feFuncA
feFuncB
feFuncG
feFuncR
feGaussianBlur
feImage
feMerge
feMergeNode
feMorphology
feOffset
fePointLight
feSpecularLighting
feSpotLight
feTile
feTurbulence
filter☑️

Font elements

ElementStatus
⚠️altGlyph
⚠️altGlyphDef
⚠️altGlyphItem
⚠️font
⚠️font-face
⚠️font-face-format
⚠️font-face-name
⚠️font-face-src
⚠️font-face-uri
⚠️glyph
⚠️glyphRef
⚠️hkern
⚠️missing-glyph
⚠️vkern

Other elements

ElementStatus
desc( ✅ )
title( ✅ )
metadata( ✅ )
color-profile
⚠️cursor
script
style☑️

Usage examples

Basic (Swing)

To render an SVG to a Swing component you can start from the following example:

importjavax.swing.*;
importjava.awt.*;
importjava.net.URL;
importjava.util.Objects;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassRenderExample {
publicstaticvoidmain(String[] args) {
SwingUtilities.invokeLater(() -> {
SVGLoaderloader = newSVGLoader();
URLsvgUrl = RenderExample.class.getResource("path/to/image.svg");
SVGDocumentdocument = loader.load(Objects.requireNonNull(svgUrl, "SVG file not found"));
JFrameframe = newJFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setPreferredSize(newDimension(400, 400));
frame.setContentPane(newSVGPanel(document));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
staticclassSVGPanelextendsJPanel {
privatefinal@NotNullSVGDocumentdocument;
SVGPanel(@NotNullSVGDocumentdocument) {
this.document = document;
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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, newViewBox(0, 0, getWidth(), getHeight()));
}
}
}

JavaFX

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

Required dependency: com.github.weisj:jsvg-javafx:2.0.1 (JavaFX 17 or later). See JavaFX renderer for the full dependency declaration.

The main entry point is FXSVGCanvas, a standard JavaFX Control that can be placed anywhere in a scene graph:

importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.ui.jfx.FXSVGCanvas;
importjavafx.application.Application;
importjavafx.scene.Scene;
importjavafx.scene.layout.StackPane;
importjavafx.stage.Stage;
publicclassFXRenderExampleextendsApplication {
@Overridepublicvoidstart(Stagestage) {
SVGLoaderloader = newSVGLoader();
SVGDocumentdocument = loader.load(getClass().getResource("path/to/image.svg"));
FXSVGCanvascanvas = newFXSVGCanvas();
// Choose the rendering backend:// RenderBackend.JavaFX - renders directly to a GraphicsContext (faster, hardware accelerated,// but some advanced features such as filters and masks may not render correctly)// RenderBackend.AWT - renders via the JSVG AWT pipeline (slower, but more accurate)canvas.setRenderBackend(FXSVGCanvas.RenderBackend.JavaFX);
canvas.setDocument(document);
stage.setScene(newScene(newStackPane(canvas), 400, 300));
stage.show();
}
publicstaticvoidmain(String[] args) {
launch(args);
}
}

FXSVGCanvas exposes JavaFX properties so it integrates naturally with bindings:

// Bind the document property to an external observablecanvas.documentProperty().bind(currentDocumentProperty);
// Show or hide the transparency checker-board pattern behind the SVGcanvas.setShowTransparentPattern(true);
// Places the svg viewport inside this region within the SVG canvas.canvas.setViewBox(newViewBox(0, 0, 200, 200));

Animations are driven automatically when animated is true (the default). You can also control playback manually:

canvas.pauseAnimation();
canvas.playAnimation();
canvas.restartAnimation();
// Disable automatic animation entirelycanvas.setAnimated(false);

For a more complete working example see FXTestViewerApplication in the test sources.

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:

<svgxmlns="http://www.w3.org/2000/svg"width="100"height="100"viewBox="0 0 100 100">
<rectx="0"y="0"width="100%"height="40%"id="myRect"></rect>
<rectx="0"y="60"width="100%"height="40%"></rect>
</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.

CustomColorsProcessorprocessor = newCustomColorsProcessor(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:

classCustomColorsProcessorimplementsDomProcessor {
privatefinalMap<String, DynamicAWTSvgPaint> customColors = newHashMap<>();
publicCustomColorsProcessor(@NotNullList<String> elementIds) {
for (StringelementId : elementIds) {
customColors.put(elementId, newDynamicAWTSvgPaint(Color.BLACK));
}
}
@NullableDynamicAWTSvgPaintcustomColorForId(@NotNullStringid) {
returncustomColors.get(id);
}
@Overridepublicvoidprocess(@NotNullDomElementroot) {
processImpl(root);
root.children().forEach(this::process);
}
privatevoidprocessImpl(@NotNullDomElementelement) {
// Obtain the id of the element.// Note: 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.StringnodeId = element.id();
if (customColors.containsKey(nodeId)) {
DynamicAWTSvgPaintdynamicColor = customColors.get(nodeId);
// This assumes the fill attribute is a plain color, not a gradient or pattern.Colorcolor = element.document().loaderContext().paintParser()
.parseColor(element.attribute("fill", "black"));
if (color == null) color = Color.BLACK;
dynamicColor.setColor(color);
// The id must be unique.StringuniqueIdForDynamicColor = UUID.randomUUID().toString();
// Register the dynamic color as a custom elementelement.document().registerNamedElement(uniqueIdForDynamicColor, dynamicColor);
// Refer to the custom element as the fill attributeelement.setAttribute("fill", uniqueIdForDynamicColor);
}
}
}
classDynamicAWTSvgPaintimplementsSimplePaintSVGPaint {
private@NotNullColorcolor;
DynamicAWTSvgPaint(@NotNullColorcolor) {
this.color = color;
}
publicvoidsetColor(@NotNullColorcolor) {
this.color = color;
}
public@NotNullColorcolor() {
returncolor;
}
@Overridepublic@NotNullPaintpaint() {
returncolor;
}
}

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:

DynamicAWTSvgPaintdynamicColor = processor.customColorForId("myRect");
SVGPanelpanel = newSVGPanel(document);
JButtonbutton = newJButton("Change color");
button.addActionListener(e -> {
ColornewColor = JColorChooser.showDialog(panel, "Choose a color", dynamicColor.color());
if (newColor != null) {
dynamicColor.setColor(newColor);
// Make sure to repaint the panel to see the changespanel.repaint();
}
});
JPanelcontent = newJPanel(newBorderLayout());
content.add(panel, BorderLayout.CENTER);
content.add(button, BorderLayout.SOUTH);
frame.setContentPane(content);

Animations (Swing)

JSVG provides a helper class AnimationPlayer for implementing animations in Swing components. The following example demonstrates how to use the AnimationPlayer to animate an SVG document:

importjavax.swing.*;
importjava.awt.*;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.renderer.animation.AnimationState;
importcom.github.weisj.jsvg.ui.AnimationPlayer;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassAnimationPanelextendsJComponent {
privatefinal@NotNullSVGDocumentdocument;
privatefinal@NotNullAnimationPlayerplayer;
publicAnimationPanel(@NotNullSVGDocumentdocument) {
this.document = document;
this.player = newAnimationPlayer(e -> repaint());
player.setAnimation(document.animation());
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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.renderWithPlatform(
newAwtComponentPlatformSupport(this),
Output.createForGraphics((Graphics2D) g),
newViewBox(0, 0, getWidth(), getHeight()),
player.animationState());
}
publicvoidstartAnimation() {
player.start();
}
publicvoidstopAnimation() {
player.stop();
}
}

Using a custom XML parser

If you need more control over how the XML source is parsed you can e.g. use a custom XMLInputFactory.

publicclassCustomXMLInputimplementsXMLInput {
privatefinal@NotNullXMLInputFactoryfactory;
privatefinal@NotNullInputStreaminputStream;
privateCustomXMLInput(@NotNullXMLInputFactoryfactory, @NotNullInputStreaminputStream) {
this.factory = factory;
this.inputStream = inputStream;
}
@Overridepublic@NotNullXMLEventReadercreateReader() throwsXMLStreamException {
returnfactory.createXMLEventReader(inputStream);
}
}
XMLInputFactoryfactory = XMLInputFactory.newFactory();
// Set up the factory to your likingURLinputUrl = ...;
SVGLoaderloader = newSVGLoader();
try (InputStreaminputStream = inputUrl.openStream()) {
SVGDocumentdocument = loader.load(
newCustomXMLInput(factory, inputStream),
inputUrl,
LoaderContext.createDefault()
);
}

About

Java SVG renderer

Topics

Resources

Contributing

Stars

221 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Quality Gate StatusCode StyleCIMaven Central

"Buy Me A Coffee"

JSVG - A Java SVG implementation

The SVG logo rendered by JSVG
The SVG logo rendered using JSVG

JSVG 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). However it does already cover most use cases and already supports more features than 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.

Table of contents

Projects using JSVG

How to use

The library is available on maven central:

dependencies {
implementation("com.github.weisj:jsvg:2.1.0")
}

Also, nightly snapshot builds will be released to maven:

repositories {
maven {
url = uri("https://central.sonatype.com/repository/maven-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 class. It will produce an SVGDocument

SVGLoaderloader = newSVGLoader();
URLsvgUrl = MyClass.class.getResource("mySvgFile.svg");
SVGDocumentsvgDocument = loader.load(svgUrl);

If you need more control over the loading process you can pass a LoaderContext for configuration purposes.

SVGDocumentsvgDocument = 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.

Rendering

An SVGDocument can be rendered to any Graphics2D object you like e.g. a BufferedImage

FloatSizesize = svgDocument.size();
BufferedImageimage = newBufferedImage((int) size.width,(int) size.height);
Graphics2Dg = image.createGraphics();
svgDocument.render(null,g);
g.dispose();

or a swing component

classMyComponentextendsJComponent {
@OverrideprotectedvoidpaintComponent(Graphicsg) {
super.paintComponent(g);
svgDocument.render(this, (Graphics2D) g, newViewBox(0, 0, getWidth(), getHeight()));
}
}

For more in-depth examples see Usage examples below.

Rendering Quality

The rendering quality can be adjusted by setting the RenderingHints of the Graphics2D object. The following properties are recommended:

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:

// Will use the value of RenderingHints.KEY_ANTIALIASING by defaultg.setRenderingHint(SVGRenderingHints.KEY_IMAGE_ANTIALIASING, SVGRenderingHints.VALUE_IMAGE_ANTIALIASING_ON);

By default clipping with a <clipPath> element does not use soft-clipping (i.e. anti-aliasing along the edges of the clip shape). This can be enabled by setting

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:

KeyValuesDefaultDescription
KEY_IMAGE_ANTIALIASINGVALUE_IMAGE_ANTIALIAS_ON
VALUE_IMAGE_ANTIALIAS_OFF
Value of RenderingHints.KEY_ANTIALIASINGEnables anti-aliasing for images
KEY_SOFT_CLIPPINGVALUE_SOFT_CLIPPING_ON
VALUE_SOFT_CLIPPING_OFF
VALUE_SOFT_CLIPPING_OFFEnables soft (anti-aliased) clipping for clipPath
KEY_MASK_CLIP_RENDERINGVALUE_MASK_CLIP_RENDERING_FAST
VALUE_MASK_CLIP_RENDERING_ACCURACY
VALUE_MASK_CLIP_RENDERING_DEFAULT
VALUE_MASK_CLIP_RENDERING_DEFAULT = VALUE_MASK_CLIP_RENDERING_FASTChanges 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_IMAGEVALUE_USE_CACHE
VALUE_NO_CACHE
VALUE_USE_CACHEWhether 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 the Animations (Swing) and JavaFX usage examples below for details.

Additional modules

JavaFX renderer (experimental)

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

JSVG provides an optional JavaFX rendering module that allows SVG documents to be displayed inside a JavaFX application. It requires JavaFX 17 or later.

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-javafx:2.0.1")
}

See the JavaFX usage example for a full code sample.

Logging

By default JSVG uses java.util.logging (JUL) for internal diagnostics. Two optional adapter modules are provided so you can route JSVG log output through your own logging framework without any additional configuration code — simply add the desired module to the classpath/module-path and the adapter is picked up automatically via ServiceLoader.

SLF4J adapter

Routes JSVG log output through any SLF4J 2.x compatible backend (Logback, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-slf4j:2.0.1")
// also add your preferred SLF4J backend, e.g.:
runtimeOnly("ch.qos.logback:logback-classic:1.5.6")
}
System.Logger adapter

Routes JSVG log output through the Java 9+ System.Logger API, which in turn delegates to whatever logging backend has been installed for the JVM (JUL, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-systemlogger:2.0.1")
}

Both adapters provide OSGi metadata and register themselves as LogManager service providers. Only one adapter should be present on the classpath at a time.

Supported features

For supported elements most of the attributes which apply to them are implemented.

  • ✅: The element is supported. Note that this doesn't mean that every attribute is supported.
  • ✅*: The element is supported, but won't have any effect (e.g. it's currently not possible to query the content of a <desc> element)
  • ☑️: The element is partially implemented and might not support most basic features of the element.
  • ❌: The element is currently not supported
  • ⚠️: The element is deprecated in the spec and has a low priority of getting implemented.
  • 🧪: The element is an experimental part of the svg 2.* spec. It may not fully behave as expected.

Shape and container elements

ElementStatus
a
circle
clipPath
defs
ellipse
foreignObject
g
image
line
marker
mask
path
polygon
polyline
rect
svg
symbol
use
view✅*

Paint server elements

ElementStatus
linearGradient
🧪meshgradient
🧪meshrow
🧪meshpatch
pattern
radialGradient
solidColor
stop

Text elements

ElementStatus
text
textPath
⚠️tref
tspan

Animation elements

ElementStatus
animate☑️
⚠️animateColor
animateMotion
animateTransform☑️
mpath
set
switch

Filter elements

ElementStatus
feBlend
feColorMatrix
feComponentTransfer
feComposite
feConvolveMatrix
feDiffuseLighting
feDisplacementMap
feDistantLight
feDropShadow
feFlood
feFuncA
feFuncB
feFuncG
feFuncR
feGaussianBlur
feImage
feMerge
feMergeNode
feMorphology
feOffset
fePointLight
feSpecularLighting
feSpotLight
feTile
feTurbulence
filter☑️

Font elements

ElementStatus
⚠️altGlyph
⚠️altGlyphDef
⚠️altGlyphItem
⚠️font
⚠️font-face
⚠️font-face-format
⚠️font-face-name
⚠️font-face-src
⚠️font-face-uri
⚠️glyph
⚠️glyphRef
⚠️hkern
⚠️missing-glyph
⚠️vkern

Other elements

ElementStatus
desc( ✅ )
title( ✅ )
metadata( ✅ )
color-profile
⚠️cursor
script
style☑️

Usage examples

Basic (Swing)

To render an SVG to a Swing component you can start from the following example:

importjavax.swing.*;
importjava.awt.*;
importjava.net.URL;
importjava.util.Objects;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassRenderExample {
publicstaticvoidmain(String[] args) {
SwingUtilities.invokeLater(() -> {
SVGLoaderloader = newSVGLoader();
URLsvgUrl = RenderExample.class.getResource("path/to/image.svg");
SVGDocumentdocument = loader.load(Objects.requireNonNull(svgUrl, "SVG file not found"));
JFrameframe = newJFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setPreferredSize(newDimension(400, 400));
frame.setContentPane(newSVGPanel(document));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
staticclassSVGPanelextendsJPanel {
privatefinal@NotNullSVGDocumentdocument;
SVGPanel(@NotNullSVGDocumentdocument) {
this.document = document;
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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, newViewBox(0, 0, getWidth(), getHeight()));
}
}
}

JavaFX

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

Required dependency: com.github.weisj:jsvg-javafx:2.0.1 (JavaFX 17 or later). See JavaFX renderer for the full dependency declaration.

The main entry point is FXSVGCanvas, a standard JavaFX Control that can be placed anywhere in a scene graph:

importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.ui.jfx.FXSVGCanvas;
importjavafx.application.Application;
importjavafx.scene.Scene;
importjavafx.scene.layout.StackPane;
importjavafx.stage.Stage;
publicclassFXRenderExampleextendsApplication {
@Overridepublicvoidstart(Stagestage) {
SVGLoaderloader = newSVGLoader();
SVGDocumentdocument = loader.load(getClass().getResource("path/to/image.svg"));
FXSVGCanvascanvas = newFXSVGCanvas();
// Choose the rendering backend:// RenderBackend.JavaFX - renders directly to a GraphicsContext (faster, hardware accelerated,// but some advanced features such as filters and masks may not render correctly)// RenderBackend.AWT - renders via the JSVG AWT pipeline (slower, but more accurate)canvas.setRenderBackend(FXSVGCanvas.RenderBackend.JavaFX);
canvas.setDocument(document);
stage.setScene(newScene(newStackPane(canvas), 400, 300));
stage.show();
}
publicstaticvoidmain(String[] args) {
launch(args);
}
}

FXSVGCanvas exposes JavaFX properties so it integrates naturally with bindings:

// Bind the document property to an external observablecanvas.documentProperty().bind(currentDocumentProperty);
// Show or hide the transparency checker-board pattern behind the SVGcanvas.setShowTransparentPattern(true);
// Places the svg viewport inside this region within the SVG canvas.canvas.setViewBox(newViewBox(0, 0, 200, 200));

Animations are driven automatically when animated is true (the default). You can also control playback manually:

canvas.pauseAnimation();
canvas.playAnimation();
canvas.restartAnimation();
// Disable automatic animation entirelycanvas.setAnimated(false);

For a more complete working example see FXTestViewerApplication in the test sources.

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:

<svgxmlns="http://www.w3.org/2000/svg"width="100"height="100"viewBox="0 0 100 100">
<rectx="0"y="0"width="100%"height="40%"id="myRect"></rect>
<rectx="0"y="60"width="100%"height="40%"></rect>
</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.

CustomColorsProcessorprocessor = newCustomColorsProcessor(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:

classCustomColorsProcessorimplementsDomProcessor {
privatefinalMap<String, DynamicAWTSvgPaint> customColors = newHashMap<>();
publicCustomColorsProcessor(@NotNullList<String> elementIds) {
for (StringelementId : elementIds) {
customColors.put(elementId, newDynamicAWTSvgPaint(Color.BLACK));
}
}
@NullableDynamicAWTSvgPaintcustomColorForId(@NotNullStringid) {
returncustomColors.get(id);
}
@Overridepublicvoidprocess(@NotNullDomElementroot) {
processImpl(root);
root.children().forEach(this::process);
}
privatevoidprocessImpl(@NotNullDomElementelement) {
// Obtain the id of the element.// Note: 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.StringnodeId = element.id();
if (customColors.containsKey(nodeId)) {
DynamicAWTSvgPaintdynamicColor = customColors.get(nodeId);
// This assumes the fill attribute is a plain color, not a gradient or pattern.Colorcolor = element.document().loaderContext().paintParser()
.parseColor(element.attribute("fill", "black"));
if (color == null) color = Color.BLACK;
dynamicColor.setColor(color);
// The id must be unique.StringuniqueIdForDynamicColor = UUID.randomUUID().toString();
// Register the dynamic color as a custom elementelement.document().registerNamedElement(uniqueIdForDynamicColor, dynamicColor);
// Refer to the custom element as the fill attributeelement.setAttribute("fill", uniqueIdForDynamicColor);
}
}
}
classDynamicAWTSvgPaintimplementsSimplePaintSVGPaint {
private@NotNullColorcolor;
DynamicAWTSvgPaint(@NotNullColorcolor) {
this.color = color;
}
publicvoidsetColor(@NotNullColorcolor) {
this.color = color;
}
public@NotNullColorcolor() {
returncolor;
}
@Overridepublic@NotNullPaintpaint() {
returncolor;
}
}

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:

DynamicAWTSvgPaintdynamicColor = processor.customColorForId("myRect");
SVGPanelpanel = newSVGPanel(document);
JButtonbutton = newJButton("Change color");
button.addActionListener(e -> {
ColornewColor = JColorChooser.showDialog(panel, "Choose a color", dynamicColor.color());
if (newColor != null) {
dynamicColor.setColor(newColor);
// Make sure to repaint the panel to see the changespanel.repaint();
}
});
JPanelcontent = newJPanel(newBorderLayout());
content.add(panel, BorderLayout.CENTER);
content.add(button, BorderLayout.SOUTH);
frame.setContentPane(content);

Animations (Swing)

JSVG provides a helper class AnimationPlayer for implementing animations in Swing components. The following example demonstrates how to use the AnimationPlayer to animate an SVG document:

importjavax.swing.*;
importjava.awt.*;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.renderer.animation.AnimationState;
importcom.github.weisj.jsvg.ui.AnimationPlayer;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassAnimationPanelextendsJComponent {
privatefinal@NotNullSVGDocumentdocument;
privatefinal@NotNullAnimationPlayerplayer;
publicAnimationPanel(@NotNullSVGDocumentdocument) {
this.document = document;
this.player = newAnimationPlayer(e -> repaint());
player.setAnimation(document.animation());
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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.renderWithPlatform(
newAwtComponentPlatformSupport(this),
Output.createForGraphics((Graphics2D) g),
newViewBox(0, 0, getWidth(), getHeight()),
player.animationState());
}
publicvoidstartAnimation() {
player.start();
}
publicvoidstopAnimation() {
player.stop();
}
}

Using a custom XML parser

If you need more control over how the XML source is parsed you can e.g. use a custom XMLInputFactory.

publicclassCustomXMLInputimplementsXMLInput {
privatefinal@NotNullXMLInputFactoryfactory;
privatefinal@NotNullInputStreaminputStream;
privateCustomXMLInput(@NotNullXMLInputFactoryfactory, @NotNullInputStreaminputStream) {
this.factory = factory;
this.inputStream = inputStream;
}
@Overridepublic@NotNullXMLEventReadercreateReader() throwsXMLStreamException {
returnfactory.createXMLEventReader(inputStream);
}
}
XMLInputFactoryfactory = XMLInputFactory.newFactory();
// Set up the factory to your likingURLinputUrl = ...;
SVGLoaderloader = newSVGLoader();
try (InputStreaminputStream = inputUrl.openStream()) {
SVGDocumentdocument = loader.load(
newCustomXMLInput(factory, inputStream),
inputUrl,
LoaderContext.createDefault()
);
}

About

Java SVG renderer

Topics

Resources

Contributing

Stars

221 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Quality Gate StatusCode StyleCIMaven Central

"Buy Me A Coffee"

JSVG - A Java SVG implementation

The SVG logo rendered by JSVG
The SVG logo rendered using JSVG

JSVG 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). However it does already cover most use cases and already supports more features than 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.

Table of contents

Projects using JSVG

How to use

The library is available on maven central:

dependencies {
implementation("com.github.weisj:jsvg:2.1.0")
}

Also, nightly snapshot builds will be released to maven:

repositories {
maven {
url = uri("https://central.sonatype.com/repository/maven-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 class. It will produce an SVGDocument

SVGLoaderloader = newSVGLoader();
URLsvgUrl = MyClass.class.getResource("mySvgFile.svg");
SVGDocumentsvgDocument = loader.load(svgUrl);

If you need more control over the loading process you can pass a LoaderContext for configuration purposes.

SVGDocumentsvgDocument = 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.

Rendering

An SVGDocument can be rendered to any Graphics2D object you like e.g. a BufferedImage

FloatSizesize = svgDocument.size();
BufferedImageimage = newBufferedImage((int) size.width,(int) size.height);
Graphics2Dg = image.createGraphics();
svgDocument.render(null,g);
g.dispose();

or a swing component

classMyComponentextendsJComponent {
@OverrideprotectedvoidpaintComponent(Graphicsg) {
super.paintComponent(g);
svgDocument.render(this, (Graphics2D) g, newViewBox(0, 0, getWidth(), getHeight()));
}
}

For more in-depth examples see Usage examples below.

Rendering Quality

The rendering quality can be adjusted by setting the RenderingHints of the Graphics2D object. The following properties are recommended:

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:

// Will use the value of RenderingHints.KEY_ANTIALIASING by defaultg.setRenderingHint(SVGRenderingHints.KEY_IMAGE_ANTIALIASING, SVGRenderingHints.VALUE_IMAGE_ANTIALIASING_ON);

By default clipping with a <clipPath> element does not use soft-clipping (i.e. anti-aliasing along the edges of the clip shape). This can be enabled by setting

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:

KeyValuesDefaultDescription
KEY_IMAGE_ANTIALIASINGVALUE_IMAGE_ANTIALIAS_ON
VALUE_IMAGE_ANTIALIAS_OFF
Value of RenderingHints.KEY_ANTIALIASINGEnables anti-aliasing for images
KEY_SOFT_CLIPPINGVALUE_SOFT_CLIPPING_ON
VALUE_SOFT_CLIPPING_OFF
VALUE_SOFT_CLIPPING_OFFEnables soft (anti-aliased) clipping for clipPath
KEY_MASK_CLIP_RENDERINGVALUE_MASK_CLIP_RENDERING_FAST
VALUE_MASK_CLIP_RENDERING_ACCURACY
VALUE_MASK_CLIP_RENDERING_DEFAULT
VALUE_MASK_CLIP_RENDERING_DEFAULT = VALUE_MASK_CLIP_RENDERING_FASTChanges 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_IMAGEVALUE_USE_CACHE
VALUE_NO_CACHE
VALUE_USE_CACHEWhether 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 the Animations (Swing) and JavaFX usage examples below for details.

Additional modules

JavaFX renderer (experimental)

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

JSVG provides an optional JavaFX rendering module that allows SVG documents to be displayed inside a JavaFX application. It requires JavaFX 17 or later.

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-javafx:2.0.1")
}

See the JavaFX usage example for a full code sample.

Logging

By default JSVG uses java.util.logging (JUL) for internal diagnostics. Two optional adapter modules are provided so you can route JSVG log output through your own logging framework without any additional configuration code — simply add the desired module to the classpath/module-path and the adapter is picked up automatically via ServiceLoader.

SLF4J adapter

Routes JSVG log output through any SLF4J 2.x compatible backend (Logback, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-slf4j:2.0.1")
// also add your preferred SLF4J backend, e.g.:
runtimeOnly("ch.qos.logback:logback-classic:1.5.6")
}
System.Logger adapter

Routes JSVG log output through the Java 9+ System.Logger API, which in turn delegates to whatever logging backend has been installed for the JVM (JUL, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-systemlogger:2.0.1")
}

Both adapters provide OSGi metadata and register themselves as LogManager service providers. Only one adapter should be present on the classpath at a time.

Supported features

For supported elements most of the attributes which apply to them are implemented.

  • ✅: The element is supported. Note that this doesn't mean that every attribute is supported.
  • ✅*: The element is supported, but won't have any effect (e.g. it's currently not possible to query the content of a <desc> element)
  • ☑️: The element is partially implemented and might not support most basic features of the element.
  • ❌: The element is currently not supported
  • ⚠️: The element is deprecated in the spec and has a low priority of getting implemented.
  • 🧪: The element is an experimental part of the svg 2.* spec. It may not fully behave as expected.

Shape and container elements

ElementStatus
a
circle
clipPath
defs
ellipse
foreignObject
g
image
line
marker
mask
path
polygon
polyline
rect
svg
symbol
use
view✅*

Paint server elements

ElementStatus
linearGradient
🧪meshgradient
🧪meshrow
🧪meshpatch
pattern
radialGradient
solidColor
stop

Text elements

ElementStatus
text
textPath
⚠️tref
tspan

Animation elements

ElementStatus
animate☑️
⚠️animateColor
animateMotion
animateTransform☑️
mpath
set
switch

Filter elements

ElementStatus
feBlend
feColorMatrix
feComponentTransfer
feComposite
feConvolveMatrix
feDiffuseLighting
feDisplacementMap
feDistantLight
feDropShadow
feFlood
feFuncA
feFuncB
feFuncG
feFuncR
feGaussianBlur
feImage
feMerge
feMergeNode
feMorphology
feOffset
fePointLight
feSpecularLighting
feSpotLight
feTile
feTurbulence
filter☑️

Font elements

ElementStatus
⚠️altGlyph
⚠️altGlyphDef
⚠️altGlyphItem
⚠️font
⚠️font-face
⚠️font-face-format
⚠️font-face-name
⚠️font-face-src
⚠️font-face-uri
⚠️glyph
⚠️glyphRef
⚠️hkern
⚠️missing-glyph
⚠️vkern

Other elements

ElementStatus
desc( ✅ )
title( ✅ )
metadata( ✅ )
color-profile
⚠️cursor
script
style☑️

Usage examples

Basic (Swing)

To render an SVG to a Swing component you can start from the following example:

importjavax.swing.*;
importjava.awt.*;
importjava.net.URL;
importjava.util.Objects;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassRenderExample {
publicstaticvoidmain(String[] args) {
SwingUtilities.invokeLater(() -> {
SVGLoaderloader = newSVGLoader();
URLsvgUrl = RenderExample.class.getResource("path/to/image.svg");
SVGDocumentdocument = loader.load(Objects.requireNonNull(svgUrl, "SVG file not found"));
JFrameframe = newJFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setPreferredSize(newDimension(400, 400));
frame.setContentPane(newSVGPanel(document));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
staticclassSVGPanelextendsJPanel {
privatefinal@NotNullSVGDocumentdocument;
SVGPanel(@NotNullSVGDocumentdocument) {
this.document = document;
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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, newViewBox(0, 0, getWidth(), getHeight()));
}
}
}

JavaFX

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

Required dependency: com.github.weisj:jsvg-javafx:2.0.1 (JavaFX 17 or later). See JavaFX renderer for the full dependency declaration.

The main entry point is FXSVGCanvas, a standard JavaFX Control that can be placed anywhere in a scene graph:

importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.ui.jfx.FXSVGCanvas;
importjavafx.application.Application;
importjavafx.scene.Scene;
importjavafx.scene.layout.StackPane;
importjavafx.stage.Stage;
publicclassFXRenderExampleextendsApplication {
@Overridepublicvoidstart(Stagestage) {
SVGLoaderloader = newSVGLoader();
SVGDocumentdocument = loader.load(getClass().getResource("path/to/image.svg"));
FXSVGCanvascanvas = newFXSVGCanvas();
// Choose the rendering backend:// RenderBackend.JavaFX - renders directly to a GraphicsContext (faster, hardware accelerated,// but some advanced features such as filters and masks may not render correctly)// RenderBackend.AWT - renders via the JSVG AWT pipeline (slower, but more accurate)canvas.setRenderBackend(FXSVGCanvas.RenderBackend.JavaFX);
canvas.setDocument(document);
stage.setScene(newScene(newStackPane(canvas), 400, 300));
stage.show();
}
publicstaticvoidmain(String[] args) {
launch(args);
}
}

FXSVGCanvas exposes JavaFX properties so it integrates naturally with bindings:

// Bind the document property to an external observablecanvas.documentProperty().bind(currentDocumentProperty);
// Show or hide the transparency checker-board pattern behind the SVGcanvas.setShowTransparentPattern(true);
// Places the svg viewport inside this region within the SVG canvas.canvas.setViewBox(newViewBox(0, 0, 200, 200));

Animations are driven automatically when animated is true (the default). You can also control playback manually:

canvas.pauseAnimation();
canvas.playAnimation();
canvas.restartAnimation();
// Disable automatic animation entirelycanvas.setAnimated(false);

For a more complete working example see FXTestViewerApplication in the test sources.

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:

<svgxmlns="http://www.w3.org/2000/svg"width="100"height="100"viewBox="0 0 100 100">
<rectx="0"y="0"width="100%"height="40%"id="myRect"></rect>
<rectx="0"y="60"width="100%"height="40%"></rect>
</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.

CustomColorsProcessorprocessor = newCustomColorsProcessor(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:

classCustomColorsProcessorimplementsDomProcessor {
privatefinalMap<String, DynamicAWTSvgPaint> customColors = newHashMap<>();
publicCustomColorsProcessor(@NotNullList<String> elementIds) {
for (StringelementId : elementIds) {
customColors.put(elementId, newDynamicAWTSvgPaint(Color.BLACK));
}
}
@NullableDynamicAWTSvgPaintcustomColorForId(@NotNullStringid) {
returncustomColors.get(id);
}
@Overridepublicvoidprocess(@NotNullDomElementroot) {
processImpl(root);
root.children().forEach(this::process);
}
privatevoidprocessImpl(@NotNullDomElementelement) {
// Obtain the id of the element.// Note: 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.StringnodeId = element.id();
if (customColors.containsKey(nodeId)) {
DynamicAWTSvgPaintdynamicColor = customColors.get(nodeId);
// This assumes the fill attribute is a plain color, not a gradient or pattern.Colorcolor = element.document().loaderContext().paintParser()
.parseColor(element.attribute("fill", "black"));
if (color == null) color = Color.BLACK;
dynamicColor.setColor(color);
// The id must be unique.StringuniqueIdForDynamicColor = UUID.randomUUID().toString();
// Register the dynamic color as a custom elementelement.document().registerNamedElement(uniqueIdForDynamicColor, dynamicColor);
// Refer to the custom element as the fill attributeelement.setAttribute("fill", uniqueIdForDynamicColor);
}
}
}
classDynamicAWTSvgPaintimplementsSimplePaintSVGPaint {
private@NotNullColorcolor;
DynamicAWTSvgPaint(@NotNullColorcolor) {
this.color = color;
}
publicvoidsetColor(@NotNullColorcolor) {
this.color = color;
}
public@NotNullColorcolor() {
returncolor;
}
@Overridepublic@NotNullPaintpaint() {
returncolor;
}
}

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:

DynamicAWTSvgPaintdynamicColor = processor.customColorForId("myRect");
SVGPanelpanel = newSVGPanel(document);
JButtonbutton = newJButton("Change color");
button.addActionListener(e -> {
ColornewColor = JColorChooser.showDialog(panel, "Choose a color", dynamicColor.color());
if (newColor != null) {
dynamicColor.setColor(newColor);
// Make sure to repaint the panel to see the changespanel.repaint();
}
});
JPanelcontent = newJPanel(newBorderLayout());
content.add(panel, BorderLayout.CENTER);
content.add(button, BorderLayout.SOUTH);
frame.setContentPane(content);

Animations (Swing)

JSVG provides a helper class AnimationPlayer for implementing animations in Swing components. The following example demonstrates how to use the AnimationPlayer to animate an SVG document:

importjavax.swing.*;
importjava.awt.*;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.renderer.animation.AnimationState;
importcom.github.weisj.jsvg.ui.AnimationPlayer;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassAnimationPanelextendsJComponent {
privatefinal@NotNullSVGDocumentdocument;
privatefinal@NotNullAnimationPlayerplayer;
publicAnimationPanel(@NotNullSVGDocumentdocument) {
this.document = document;
this.player = newAnimationPlayer(e -> repaint());
player.setAnimation(document.animation());
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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.renderWithPlatform(
newAwtComponentPlatformSupport(this),
Output.createForGraphics((Graphics2D) g),
newViewBox(0, 0, getWidth(), getHeight()),
player.animationState());
}
publicvoidstartAnimation() {
player.start();
}
publicvoidstopAnimation() {
player.stop();
}
}

Using a custom XML parser

If you need more control over how the XML source is parsed you can e.g. use a custom XMLInputFactory.

publicclassCustomXMLInputimplementsXMLInput {
privatefinal@NotNullXMLInputFactoryfactory;
privatefinal@NotNullInputStreaminputStream;
privateCustomXMLInput(@NotNullXMLInputFactoryfactory, @NotNullInputStreaminputStream) {
this.factory = factory;
this.inputStream = inputStream;
}
@Overridepublic@NotNullXMLEventReadercreateReader() throwsXMLStreamException {
returnfactory.createXMLEventReader(inputStream);
}
}
XMLInputFactoryfactory = XMLInputFactory.newFactory();
// Set up the factory to your likingURLinputUrl = ...;
SVGLoaderloader = newSVGLoader();
try (InputStreaminputStream = inputUrl.openStream()) {
SVGDocumentdocument = loader.load(
newCustomXMLInput(factory, inputStream),
inputUrl,
LoaderContext.createDefault()
);
}

About

Java SVG renderer

Topics

Resources

Contributing

Stars

221 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Quality Gate StatusCode StyleCIMaven Central

"Buy Me A Coffee"

JSVG - A Java SVG implementation

The SVG logo rendered by JSVG
The SVG logo rendered using JSVG

JSVG 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). However it does already cover most use cases and already supports more features than 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.

Table of contents

Projects using JSVG

How to use

The library is available on maven central:

dependencies {
implementation("com.github.weisj:jsvg:2.1.0")
}

Also, nightly snapshot builds will be released to maven:

repositories {
maven {
url = uri("https://central.sonatype.com/repository/maven-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 class. It will produce an SVGDocument

SVGLoaderloader = newSVGLoader();
URLsvgUrl = MyClass.class.getResource("mySvgFile.svg");
SVGDocumentsvgDocument = loader.load(svgUrl);

If you need more control over the loading process you can pass a LoaderContext for configuration purposes.

SVGDocumentsvgDocument = 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.

Rendering

An SVGDocument can be rendered to any Graphics2D object you like e.g. a BufferedImage

FloatSizesize = svgDocument.size();
BufferedImageimage = newBufferedImage((int) size.width,(int) size.height);
Graphics2Dg = image.createGraphics();
svgDocument.render(null,g);
g.dispose();

or a swing component

classMyComponentextendsJComponent {
@OverrideprotectedvoidpaintComponent(Graphicsg) {
super.paintComponent(g);
svgDocument.render(this, (Graphics2D) g, newViewBox(0, 0, getWidth(), getHeight()));
}
}

For more in-depth examples see Usage examples below.

Rendering Quality

The rendering quality can be adjusted by setting the RenderingHints of the Graphics2D object. The following properties are recommended:

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:

// Will use the value of RenderingHints.KEY_ANTIALIASING by defaultg.setRenderingHint(SVGRenderingHints.KEY_IMAGE_ANTIALIASING, SVGRenderingHints.VALUE_IMAGE_ANTIALIASING_ON);

By default clipping with a <clipPath> element does not use soft-clipping (i.e. anti-aliasing along the edges of the clip shape). This can be enabled by setting

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:

KeyValuesDefaultDescription
KEY_IMAGE_ANTIALIASINGVALUE_IMAGE_ANTIALIAS_ON
VALUE_IMAGE_ANTIALIAS_OFF
Value of RenderingHints.KEY_ANTIALIASINGEnables anti-aliasing for images
KEY_SOFT_CLIPPINGVALUE_SOFT_CLIPPING_ON
VALUE_SOFT_CLIPPING_OFF
VALUE_SOFT_CLIPPING_OFFEnables soft (anti-aliased) clipping for clipPath
KEY_MASK_CLIP_RENDERINGVALUE_MASK_CLIP_RENDERING_FAST
VALUE_MASK_CLIP_RENDERING_ACCURACY
VALUE_MASK_CLIP_RENDERING_DEFAULT
VALUE_MASK_CLIP_RENDERING_DEFAULT = VALUE_MASK_CLIP_RENDERING_FASTChanges 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_IMAGEVALUE_USE_CACHE
VALUE_NO_CACHE
VALUE_USE_CACHEWhether 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 the Animations (Swing) and JavaFX usage examples below for details.

Additional modules

JavaFX renderer (experimental)

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

JSVG provides an optional JavaFX rendering module that allows SVG documents to be displayed inside a JavaFX application. It requires JavaFX 17 or later.

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-javafx:2.0.1")
}

See the JavaFX usage example for a full code sample.

Logging

By default JSVG uses java.util.logging (JUL) for internal diagnostics. Two optional adapter modules are provided so you can route JSVG log output through your own logging framework without any additional configuration code — simply add the desired module to the classpath/module-path and the adapter is picked up automatically via ServiceLoader.

SLF4J adapter

Routes JSVG log output through any SLF4J 2.x compatible backend (Logback, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-slf4j:2.0.1")
// also add your preferred SLF4J backend, e.g.:
runtimeOnly("ch.qos.logback:logback-classic:1.5.6")
}
System.Logger adapter

Routes JSVG log output through the Java 9+ System.Logger API, which in turn delegates to whatever logging backend has been installed for the JVM (JUL, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-systemlogger:2.0.1")
}

Both adapters provide OSGi metadata and register themselves as LogManager service providers. Only one adapter should be present on the classpath at a time.

Supported features

For supported elements most of the attributes which apply to them are implemented.

  • ✅: The element is supported. Note that this doesn't mean that every attribute is supported.
  • ✅*: The element is supported, but won't have any effect (e.g. it's currently not possible to query the content of a <desc> element)
  • ☑️: The element is partially implemented and might not support most basic features of the element.
  • ❌: The element is currently not supported
  • ⚠️: The element is deprecated in the spec and has a low priority of getting implemented.
  • 🧪: The element is an experimental part of the svg 2.* spec. It may not fully behave as expected.

Shape and container elements

ElementStatus
a
circle
clipPath
defs
ellipse
foreignObject
g
image
line
marker
mask
path
polygon
polyline
rect
svg
symbol
use
view✅*

Paint server elements

ElementStatus
linearGradient
🧪meshgradient
🧪meshrow
🧪meshpatch
pattern
radialGradient
solidColor
stop

Text elements

ElementStatus
text
textPath
⚠️tref
tspan

Animation elements

ElementStatus
animate☑️
⚠️animateColor
animateMotion
animateTransform☑️
mpath
set
switch

Filter elements

ElementStatus
feBlend
feColorMatrix
feComponentTransfer
feComposite
feConvolveMatrix
feDiffuseLighting
feDisplacementMap
feDistantLight
feDropShadow
feFlood
feFuncA
feFuncB
feFuncG
feFuncR
feGaussianBlur
feImage
feMerge
feMergeNode
feMorphology
feOffset
fePointLight
feSpecularLighting
feSpotLight
feTile
feTurbulence
filter☑️

Font elements

ElementStatus
⚠️altGlyph
⚠️altGlyphDef
⚠️altGlyphItem
⚠️font
⚠️font-face
⚠️font-face-format
⚠️font-face-name
⚠️font-face-src
⚠️font-face-uri
⚠️glyph
⚠️glyphRef
⚠️hkern
⚠️missing-glyph
⚠️vkern

Other elements

ElementStatus
desc( ✅ )
title( ✅ )
metadata( ✅ )
color-profile
⚠️cursor
script
style☑️

Usage examples

Basic (Swing)

To render an SVG to a Swing component you can start from the following example:

importjavax.swing.*;
importjava.awt.*;
importjava.net.URL;
importjava.util.Objects;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassRenderExample {
publicstaticvoidmain(String[] args) {
SwingUtilities.invokeLater(() -> {
SVGLoaderloader = newSVGLoader();
URLsvgUrl = RenderExample.class.getResource("path/to/image.svg");
SVGDocumentdocument = loader.load(Objects.requireNonNull(svgUrl, "SVG file not found"));
JFrameframe = newJFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setPreferredSize(newDimension(400, 400));
frame.setContentPane(newSVGPanel(document));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
staticclassSVGPanelextendsJPanel {
privatefinal@NotNullSVGDocumentdocument;
SVGPanel(@NotNullSVGDocumentdocument) {
this.document = document;
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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, newViewBox(0, 0, getWidth(), getHeight()));
}
}
}

JavaFX

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

Required dependency: com.github.weisj:jsvg-javafx:2.0.1 (JavaFX 17 or later). See JavaFX renderer for the full dependency declaration.

The main entry point is FXSVGCanvas, a standard JavaFX Control that can be placed anywhere in a scene graph:

importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.ui.jfx.FXSVGCanvas;
importjavafx.application.Application;
importjavafx.scene.Scene;
importjavafx.scene.layout.StackPane;
importjavafx.stage.Stage;
publicclassFXRenderExampleextendsApplication {
@Overridepublicvoidstart(Stagestage) {
SVGLoaderloader = newSVGLoader();
SVGDocumentdocument = loader.load(getClass().getResource("path/to/image.svg"));
FXSVGCanvascanvas = newFXSVGCanvas();
// Choose the rendering backend:// RenderBackend.JavaFX - renders directly to a GraphicsContext (faster, hardware accelerated,// but some advanced features such as filters and masks may not render correctly)// RenderBackend.AWT - renders via the JSVG AWT pipeline (slower, but more accurate)canvas.setRenderBackend(FXSVGCanvas.RenderBackend.JavaFX);
canvas.setDocument(document);
stage.setScene(newScene(newStackPane(canvas), 400, 300));
stage.show();
}
publicstaticvoidmain(String[] args) {
launch(args);
}
}

FXSVGCanvas exposes JavaFX properties so it integrates naturally with bindings:

// Bind the document property to an external observablecanvas.documentProperty().bind(currentDocumentProperty);
// Show or hide the transparency checker-board pattern behind the SVGcanvas.setShowTransparentPattern(true);
// Places the svg viewport inside this region within the SVG canvas.canvas.setViewBox(newViewBox(0, 0, 200, 200));

Animations are driven automatically when animated is true (the default). You can also control playback manually:

canvas.pauseAnimation();
canvas.playAnimation();
canvas.restartAnimation();
// Disable automatic animation entirelycanvas.setAnimated(false);

For a more complete working example see FXTestViewerApplication in the test sources.

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:

<svgxmlns="http://www.w3.org/2000/svg"width="100"height="100"viewBox="0 0 100 100">
<rectx="0"y="0"width="100%"height="40%"id="myRect"></rect>
<rectx="0"y="60"width="100%"height="40%"></rect>
</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.

CustomColorsProcessorprocessor = newCustomColorsProcessor(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:

classCustomColorsProcessorimplementsDomProcessor {
privatefinalMap<String, DynamicAWTSvgPaint> customColors = newHashMap<>();
publicCustomColorsProcessor(@NotNullList<String> elementIds) {
for (StringelementId : elementIds) {
customColors.put(elementId, newDynamicAWTSvgPaint(Color.BLACK));
}
}
@NullableDynamicAWTSvgPaintcustomColorForId(@NotNullStringid) {
returncustomColors.get(id);
}
@Overridepublicvoidprocess(@NotNullDomElementroot) {
processImpl(root);
root.children().forEach(this::process);
}
privatevoidprocessImpl(@NotNullDomElementelement) {
// Obtain the id of the element.// Note: 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.StringnodeId = element.id();
if (customColors.containsKey(nodeId)) {
DynamicAWTSvgPaintdynamicColor = customColors.get(nodeId);
// This assumes the fill attribute is a plain color, not a gradient or pattern.Colorcolor = element.document().loaderContext().paintParser()
.parseColor(element.attribute("fill", "black"));
if (color == null) color = Color.BLACK;
dynamicColor.setColor(color);
// The id must be unique.StringuniqueIdForDynamicColor = UUID.randomUUID().toString();
// Register the dynamic color as a custom elementelement.document().registerNamedElement(uniqueIdForDynamicColor, dynamicColor);
// Refer to the custom element as the fill attributeelement.setAttribute("fill", uniqueIdForDynamicColor);
}
}
}
classDynamicAWTSvgPaintimplementsSimplePaintSVGPaint {
private@NotNullColorcolor;
DynamicAWTSvgPaint(@NotNullColorcolor) {
this.color = color;
}
publicvoidsetColor(@NotNullColorcolor) {
this.color = color;
}
public@NotNullColorcolor() {
returncolor;
}
@Overridepublic@NotNullPaintpaint() {
returncolor;
}
}

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:

DynamicAWTSvgPaintdynamicColor = processor.customColorForId("myRect");
SVGPanelpanel = newSVGPanel(document);
JButtonbutton = newJButton("Change color");
button.addActionListener(e -> {
ColornewColor = JColorChooser.showDialog(panel, "Choose a color", dynamicColor.color());
if (newColor != null) {
dynamicColor.setColor(newColor);
// Make sure to repaint the panel to see the changespanel.repaint();
}
});
JPanelcontent = newJPanel(newBorderLayout());
content.add(panel, BorderLayout.CENTER);
content.add(button, BorderLayout.SOUTH);
frame.setContentPane(content);

Animations (Swing)

JSVG provides a helper class AnimationPlayer for implementing animations in Swing components. The following example demonstrates how to use the AnimationPlayer to animate an SVG document:

importjavax.swing.*;
importjava.awt.*;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.renderer.animation.AnimationState;
importcom.github.weisj.jsvg.ui.AnimationPlayer;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassAnimationPanelextendsJComponent {
privatefinal@NotNullSVGDocumentdocument;
privatefinal@NotNullAnimationPlayerplayer;
publicAnimationPanel(@NotNullSVGDocumentdocument) {
this.document = document;
this.player = newAnimationPlayer(e -> repaint());
player.setAnimation(document.animation());
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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.renderWithPlatform(
newAwtComponentPlatformSupport(this),
Output.createForGraphics((Graphics2D) g),
newViewBox(0, 0, getWidth(), getHeight()),
player.animationState());
}
publicvoidstartAnimation() {
player.start();
}
publicvoidstopAnimation() {
player.stop();
}
}

Using a custom XML parser

If you need more control over how the XML source is parsed you can e.g. use a custom XMLInputFactory.

publicclassCustomXMLInputimplementsXMLInput {
privatefinal@NotNullXMLInputFactoryfactory;
privatefinal@NotNullInputStreaminputStream;
privateCustomXMLInput(@NotNullXMLInputFactoryfactory, @NotNullInputStreaminputStream) {
this.factory = factory;
this.inputStream = inputStream;
}
@Overridepublic@NotNullXMLEventReadercreateReader() throwsXMLStreamException {
returnfactory.createXMLEventReader(inputStream);
}
}
XMLInputFactoryfactory = XMLInputFactory.newFactory();
// Set up the factory to your likingURLinputUrl = ...;
SVGLoaderloader = newSVGLoader();
try (InputStreaminputStream = inputUrl.openStream()) {
SVGDocumentdocument = loader.load(
newCustomXMLInput(factory, inputStream),
inputUrl,
LoaderContext.createDefault()
);
}

About

Java SVG renderer

Topics

Resources

Contributing

Stars

221 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Quality Gate StatusCode StyleCIMaven Central

"Buy Me A Coffee"

JSVG - A Java SVG implementation

The SVG logo rendered by JSVG
The SVG logo rendered using JSVG

JSVG 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). However it does already cover most use cases and already supports more features than 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.

Table of contents

Projects using JSVG

How to use

The library is available on maven central:

dependencies {
implementation("com.github.weisj:jsvg:2.1.0")
}

Also, nightly snapshot builds will be released to maven:

repositories {
maven {
url = uri("https://central.sonatype.com/repository/maven-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 class. It will produce an SVGDocument

SVGLoaderloader = newSVGLoader();
URLsvgUrl = MyClass.class.getResource("mySvgFile.svg");
SVGDocumentsvgDocument = loader.load(svgUrl);

If you need more control over the loading process you can pass a LoaderContext for configuration purposes.

SVGDocumentsvgDocument = 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.

Rendering

An SVGDocument can be rendered to any Graphics2D object you like e.g. a BufferedImage

FloatSizesize = svgDocument.size();
BufferedImageimage = newBufferedImage((int) size.width,(int) size.height);
Graphics2Dg = image.createGraphics();
svgDocument.render(null,g);
g.dispose();

or a swing component

classMyComponentextendsJComponent {
@OverrideprotectedvoidpaintComponent(Graphicsg) {
super.paintComponent(g);
svgDocument.render(this, (Graphics2D) g, newViewBox(0, 0, getWidth(), getHeight()));
}
}

For more in-depth examples see Usage examples below.

Rendering Quality

The rendering quality can be adjusted by setting the RenderingHints of the Graphics2D object. The following properties are recommended:

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:

// Will use the value of RenderingHints.KEY_ANTIALIASING by defaultg.setRenderingHint(SVGRenderingHints.KEY_IMAGE_ANTIALIASING, SVGRenderingHints.VALUE_IMAGE_ANTIALIASING_ON);

By default clipping with a <clipPath> element does not use soft-clipping (i.e. anti-aliasing along the edges of the clip shape). This can be enabled by setting

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:

KeyValuesDefaultDescription
KEY_IMAGE_ANTIALIASINGVALUE_IMAGE_ANTIALIAS_ON
VALUE_IMAGE_ANTIALIAS_OFF
Value of RenderingHints.KEY_ANTIALIASINGEnables anti-aliasing for images
KEY_SOFT_CLIPPINGVALUE_SOFT_CLIPPING_ON
VALUE_SOFT_CLIPPING_OFF
VALUE_SOFT_CLIPPING_OFFEnables soft (anti-aliased) clipping for clipPath
KEY_MASK_CLIP_RENDERINGVALUE_MASK_CLIP_RENDERING_FAST
VALUE_MASK_CLIP_RENDERING_ACCURACY
VALUE_MASK_CLIP_RENDERING_DEFAULT
VALUE_MASK_CLIP_RENDERING_DEFAULT = VALUE_MASK_CLIP_RENDERING_FASTChanges 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_IMAGEVALUE_USE_CACHE
VALUE_NO_CACHE
VALUE_USE_CACHEWhether 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 the Animations (Swing) and JavaFX usage examples below for details.

Additional modules

JavaFX renderer (experimental)

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

JSVG provides an optional JavaFX rendering module that allows SVG documents to be displayed inside a JavaFX application. It requires JavaFX 17 or later.

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-javafx:2.0.1")
}

See the JavaFX usage example for a full code sample.

Logging

By default JSVG uses java.util.logging (JUL) for internal diagnostics. Two optional adapter modules are provided so you can route JSVG log output through your own logging framework without any additional configuration code — simply add the desired module to the classpath/module-path and the adapter is picked up automatically via ServiceLoader.

SLF4J adapter

Routes JSVG log output through any SLF4J 2.x compatible backend (Logback, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-slf4j:2.0.1")
// also add your preferred SLF4J backend, e.g.:
runtimeOnly("ch.qos.logback:logback-classic:1.5.6")
}
System.Logger adapter

Routes JSVG log output through the Java 9+ System.Logger API, which in turn delegates to whatever logging backend has been installed for the JVM (JUL, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-systemlogger:2.0.1")
}

Both adapters provide OSGi metadata and register themselves as LogManager service providers. Only one adapter should be present on the classpath at a time.

Supported features

For supported elements most of the attributes which apply to them are implemented.

  • ✅: The element is supported. Note that this doesn't mean that every attribute is supported.
  • ✅*: The element is supported, but won't have any effect (e.g. it's currently not possible to query the content of a <desc> element)
  • ☑️: The element is partially implemented and might not support most basic features of the element.
  • ❌: The element is currently not supported
  • ⚠️: The element is deprecated in the spec and has a low priority of getting implemented.
  • 🧪: The element is an experimental part of the svg 2.* spec. It may not fully behave as expected.

Shape and container elements

ElementStatus
a
circle
clipPath
defs
ellipse
foreignObject
g
image
line
marker
mask
path
polygon
polyline
rect
svg
symbol
use
view✅*

Paint server elements

ElementStatus
linearGradient
🧪meshgradient
🧪meshrow
🧪meshpatch
pattern
radialGradient
solidColor
stop

Text elements

ElementStatus
text
textPath
⚠️tref
tspan

Animation elements

ElementStatus
animate☑️
⚠️animateColor
animateMotion
animateTransform☑️
mpath
set
switch

Filter elements

ElementStatus
feBlend
feColorMatrix
feComponentTransfer
feComposite
feConvolveMatrix
feDiffuseLighting
feDisplacementMap
feDistantLight
feDropShadow
feFlood
feFuncA
feFuncB
feFuncG
feFuncR
feGaussianBlur
feImage
feMerge
feMergeNode
feMorphology
feOffset
fePointLight
feSpecularLighting
feSpotLight
feTile
feTurbulence
filter☑️

Font elements

ElementStatus
⚠️altGlyph
⚠️altGlyphDef
⚠️altGlyphItem
⚠️font
⚠️font-face
⚠️font-face-format
⚠️font-face-name
⚠️font-face-src
⚠️font-face-uri
⚠️glyph
⚠️glyphRef
⚠️hkern
⚠️missing-glyph
⚠️vkern

Other elements

ElementStatus
desc( ✅ )
title( ✅ )
metadata( ✅ )
color-profile
⚠️cursor
script
style☑️

Usage examples

Basic (Swing)

To render an SVG to a Swing component you can start from the following example:

importjavax.swing.*;
importjava.awt.*;
importjava.net.URL;
importjava.util.Objects;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassRenderExample {
publicstaticvoidmain(String[] args) {
SwingUtilities.invokeLater(() -> {
SVGLoaderloader = newSVGLoader();
URLsvgUrl = RenderExample.class.getResource("path/to/image.svg");
SVGDocumentdocument = loader.load(Objects.requireNonNull(svgUrl, "SVG file not found"));
JFrameframe = newJFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setPreferredSize(newDimension(400, 400));
frame.setContentPane(newSVGPanel(document));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
staticclassSVGPanelextendsJPanel {
privatefinal@NotNullSVGDocumentdocument;
SVGPanel(@NotNullSVGDocumentdocument) {
this.document = document;
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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, newViewBox(0, 0, getWidth(), getHeight()));
}
}
}

JavaFX

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

Required dependency: com.github.weisj:jsvg-javafx:2.0.1 (JavaFX 17 or later). See JavaFX renderer for the full dependency declaration.

The main entry point is FXSVGCanvas, a standard JavaFX Control that can be placed anywhere in a scene graph:

importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.ui.jfx.FXSVGCanvas;
importjavafx.application.Application;
importjavafx.scene.Scene;
importjavafx.scene.layout.StackPane;
importjavafx.stage.Stage;
publicclassFXRenderExampleextendsApplication {
@Overridepublicvoidstart(Stagestage) {
SVGLoaderloader = newSVGLoader();
SVGDocumentdocument = loader.load(getClass().getResource("path/to/image.svg"));
FXSVGCanvascanvas = newFXSVGCanvas();
// Choose the rendering backend:// RenderBackend.JavaFX - renders directly to a GraphicsContext (faster, hardware accelerated,// but some advanced features such as filters and masks may not render correctly)// RenderBackend.AWT - renders via the JSVG AWT pipeline (slower, but more accurate)canvas.setRenderBackend(FXSVGCanvas.RenderBackend.JavaFX);
canvas.setDocument(document);
stage.setScene(newScene(newStackPane(canvas), 400, 300));
stage.show();
}
publicstaticvoidmain(String[] args) {
launch(args);
}
}

FXSVGCanvas exposes JavaFX properties so it integrates naturally with bindings:

// Bind the document property to an external observablecanvas.documentProperty().bind(currentDocumentProperty);
// Show or hide the transparency checker-board pattern behind the SVGcanvas.setShowTransparentPattern(true);
// Places the svg viewport inside this region within the SVG canvas.canvas.setViewBox(newViewBox(0, 0, 200, 200));

Animations are driven automatically when animated is true (the default). You can also control playback manually:

canvas.pauseAnimation();
canvas.playAnimation();
canvas.restartAnimation();
// Disable automatic animation entirelycanvas.setAnimated(false);

For a more complete working example see FXTestViewerApplication in the test sources.

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:

<svgxmlns="http://www.w3.org/2000/svg"width="100"height="100"viewBox="0 0 100 100">
<rectx="0"y="0"width="100%"height="40%"id="myRect"></rect>
<rectx="0"y="60"width="100%"height="40%"></rect>
</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.

CustomColorsProcessorprocessor = newCustomColorsProcessor(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:

classCustomColorsProcessorimplementsDomProcessor {
privatefinalMap<String, DynamicAWTSvgPaint> customColors = newHashMap<>();
publicCustomColorsProcessor(@NotNullList<String> elementIds) {
for (StringelementId : elementIds) {
customColors.put(elementId, newDynamicAWTSvgPaint(Color.BLACK));
}
}
@NullableDynamicAWTSvgPaintcustomColorForId(@NotNullStringid) {
returncustomColors.get(id);
}
@Overridepublicvoidprocess(@NotNullDomElementroot) {
processImpl(root);
root.children().forEach(this::process);
}
privatevoidprocessImpl(@NotNullDomElementelement) {
// Obtain the id of the element.// Note: 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.StringnodeId = element.id();
if (customColors.containsKey(nodeId)) {
DynamicAWTSvgPaintdynamicColor = customColors.get(nodeId);
// This assumes the fill attribute is a plain color, not a gradient or pattern.Colorcolor = element.document().loaderContext().paintParser()
.parseColor(element.attribute("fill", "black"));
if (color == null) color = Color.BLACK;
dynamicColor.setColor(color);
// The id must be unique.StringuniqueIdForDynamicColor = UUID.randomUUID().toString();
// Register the dynamic color as a custom elementelement.document().registerNamedElement(uniqueIdForDynamicColor, dynamicColor);
// Refer to the custom element as the fill attributeelement.setAttribute("fill", uniqueIdForDynamicColor);
}
}
}
classDynamicAWTSvgPaintimplementsSimplePaintSVGPaint {
private@NotNullColorcolor;
DynamicAWTSvgPaint(@NotNullColorcolor) {
this.color = color;
}
publicvoidsetColor(@NotNullColorcolor) {
this.color = color;
}
public@NotNullColorcolor() {
returncolor;
}
@Overridepublic@NotNullPaintpaint() {
returncolor;
}
}

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:

DynamicAWTSvgPaintdynamicColor = processor.customColorForId("myRect");
SVGPanelpanel = newSVGPanel(document);
JButtonbutton = newJButton("Change color");
button.addActionListener(e -> {
ColornewColor = JColorChooser.showDialog(panel, "Choose a color", dynamicColor.color());
if (newColor != null) {
dynamicColor.setColor(newColor);
// Make sure to repaint the panel to see the changespanel.repaint();
}
});
JPanelcontent = newJPanel(newBorderLayout());
content.add(panel, BorderLayout.CENTER);
content.add(button, BorderLayout.SOUTH);
frame.setContentPane(content);

Animations (Swing)

JSVG provides a helper class AnimationPlayer for implementing animations in Swing components. The following example demonstrates how to use the AnimationPlayer to animate an SVG document:

importjavax.swing.*;
importjava.awt.*;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.renderer.animation.AnimationState;
importcom.github.weisj.jsvg.ui.AnimationPlayer;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassAnimationPanelextendsJComponent {
privatefinal@NotNullSVGDocumentdocument;
privatefinal@NotNullAnimationPlayerplayer;
publicAnimationPanel(@NotNullSVGDocumentdocument) {
this.document = document;
this.player = newAnimationPlayer(e -> repaint());
player.setAnimation(document.animation());
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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.renderWithPlatform(
newAwtComponentPlatformSupport(this),
Output.createForGraphics((Graphics2D) g),
newViewBox(0, 0, getWidth(), getHeight()),
player.animationState());
}
publicvoidstartAnimation() {
player.start();
}
publicvoidstopAnimation() {
player.stop();
}
}

Using a custom XML parser

If you need more control over how the XML source is parsed you can e.g. use a custom XMLInputFactory.

publicclassCustomXMLInputimplementsXMLInput {
privatefinal@NotNullXMLInputFactoryfactory;
privatefinal@NotNullInputStreaminputStream;
privateCustomXMLInput(@NotNullXMLInputFactoryfactory, @NotNullInputStreaminputStream) {
this.factory = factory;
this.inputStream = inputStream;
}
@Overridepublic@NotNullXMLEventReadercreateReader() throwsXMLStreamException {
returnfactory.createXMLEventReader(inputStream);
}
}
XMLInputFactoryfactory = XMLInputFactory.newFactory();
// Set up the factory to your likingURLinputUrl = ...;
SVGLoaderloader = newSVGLoader();
try (InputStreaminputStream = inputUrl.openStream()) {
SVGDocumentdocument = loader.load(
newCustomXMLInput(factory, inputStream),
inputUrl,
LoaderContext.createDefault()
);
}

About

Java SVG renderer

Topics

Resources

Contributing

Stars

221 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Quality Gate StatusCode StyleCIMaven Central

"Buy Me A Coffee"

JSVG - A Java SVG implementation

The SVG logo rendered by JSVG
The SVG logo rendered using JSVG

JSVG 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). However it does already cover most use cases and already supports more features than 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.

Table of contents

Projects using JSVG

How to use

The library is available on maven central:

dependencies {
implementation("com.github.weisj:jsvg:2.1.0")
}

Also, nightly snapshot builds will be released to maven:

repositories {
maven {
url = uri("https://central.sonatype.com/repository/maven-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 class. It will produce an SVGDocument

SVGLoaderloader = newSVGLoader();
URLsvgUrl = MyClass.class.getResource("mySvgFile.svg");
SVGDocumentsvgDocument = loader.load(svgUrl);

If you need more control over the loading process you can pass a LoaderContext for configuration purposes.

SVGDocumentsvgDocument = 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.

Rendering

An SVGDocument can be rendered to any Graphics2D object you like e.g. a BufferedImage

FloatSizesize = svgDocument.size();
BufferedImageimage = newBufferedImage((int) size.width,(int) size.height);
Graphics2Dg = image.createGraphics();
svgDocument.render(null,g);
g.dispose();

or a swing component

classMyComponentextendsJComponent {
@OverrideprotectedvoidpaintComponent(Graphicsg) {
super.paintComponent(g);
svgDocument.render(this, (Graphics2D) g, newViewBox(0, 0, getWidth(), getHeight()));
}
}

For more in-depth examples see Usage examples below.

Rendering Quality

The rendering quality can be adjusted by setting the RenderingHints of the Graphics2D object. The following properties are recommended:

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:

// Will use the value of RenderingHints.KEY_ANTIALIASING by defaultg.setRenderingHint(SVGRenderingHints.KEY_IMAGE_ANTIALIASING, SVGRenderingHints.VALUE_IMAGE_ANTIALIASING_ON);

By default clipping with a <clipPath> element does not use soft-clipping (i.e. anti-aliasing along the edges of the clip shape). This can be enabled by setting

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:

KeyValuesDefaultDescription
KEY_IMAGE_ANTIALIASINGVALUE_IMAGE_ANTIALIAS_ON
VALUE_IMAGE_ANTIALIAS_OFF
Value of RenderingHints.KEY_ANTIALIASINGEnables anti-aliasing for images
KEY_SOFT_CLIPPINGVALUE_SOFT_CLIPPING_ON
VALUE_SOFT_CLIPPING_OFF
VALUE_SOFT_CLIPPING_OFFEnables soft (anti-aliased) clipping for clipPath
KEY_MASK_CLIP_RENDERINGVALUE_MASK_CLIP_RENDERING_FAST
VALUE_MASK_CLIP_RENDERING_ACCURACY
VALUE_MASK_CLIP_RENDERING_DEFAULT
VALUE_MASK_CLIP_RENDERING_DEFAULT = VALUE_MASK_CLIP_RENDERING_FASTChanges 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_IMAGEVALUE_USE_CACHE
VALUE_NO_CACHE
VALUE_USE_CACHEWhether 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 the Animations (Swing) and JavaFX usage examples below for details.

Additional modules

JavaFX renderer (experimental)

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

JSVG provides an optional JavaFX rendering module that allows SVG documents to be displayed inside a JavaFX application. It requires JavaFX 17 or later.

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-javafx:2.0.1")
}

See the JavaFX usage example for a full code sample.

Logging

By default JSVG uses java.util.logging (JUL) for internal diagnostics. Two optional adapter modules are provided so you can route JSVG log output through your own logging framework without any additional configuration code — simply add the desired module to the classpath/module-path and the adapter is picked up automatically via ServiceLoader.

SLF4J adapter

Routes JSVG log output through any SLF4J 2.x compatible backend (Logback, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-slf4j:2.0.1")
// also add your preferred SLF4J backend, e.g.:
runtimeOnly("ch.qos.logback:logback-classic:1.5.6")
}
System.Logger adapter

Routes JSVG log output through the Java 9+ System.Logger API, which in turn delegates to whatever logging backend has been installed for the JVM (JUL, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-systemlogger:2.0.1")
}

Both adapters provide OSGi metadata and register themselves as LogManager service providers. Only one adapter should be present on the classpath at a time.

Supported features

For supported elements most of the attributes which apply to them are implemented.

  • ✅: The element is supported. Note that this doesn't mean that every attribute is supported.
  • ✅*: The element is supported, but won't have any effect (e.g. it's currently not possible to query the content of a <desc> element)
  • ☑️: The element is partially implemented and might not support most basic features of the element.
  • ❌: The element is currently not supported
  • ⚠️: The element is deprecated in the spec and has a low priority of getting implemented.
  • 🧪: The element is an experimental part of the svg 2.* spec. It may not fully behave as expected.

Shape and container elements

ElementStatus
a
circle
clipPath
defs
ellipse
foreignObject
g
image
line
marker
mask
path
polygon
polyline
rect
svg
symbol
use
view✅*

Paint server elements

ElementStatus
linearGradient
🧪meshgradient
🧪meshrow
🧪meshpatch
pattern
radialGradient
solidColor
stop

Text elements

ElementStatus
text
textPath
⚠️tref
tspan

Animation elements

ElementStatus
animate☑️
⚠️animateColor
animateMotion
animateTransform☑️
mpath
set
switch

Filter elements

ElementStatus
feBlend
feColorMatrix
feComponentTransfer
feComposite
feConvolveMatrix
feDiffuseLighting
feDisplacementMap
feDistantLight
feDropShadow
feFlood
feFuncA
feFuncB
feFuncG
feFuncR
feGaussianBlur
feImage
feMerge
feMergeNode
feMorphology
feOffset
fePointLight
feSpecularLighting
feSpotLight
feTile
feTurbulence
filter☑️

Font elements

ElementStatus
⚠️altGlyph
⚠️altGlyphDef
⚠️altGlyphItem
⚠️font
⚠️font-face
⚠️font-face-format
⚠️font-face-name
⚠️font-face-src
⚠️font-face-uri
⚠️glyph
⚠️glyphRef
⚠️hkern
⚠️missing-glyph
⚠️vkern

Other elements

ElementStatus
desc( ✅ )
title( ✅ )
metadata( ✅ )
color-profile
⚠️cursor
script
style☑️

Usage examples

Basic (Swing)

To render an SVG to a Swing component you can start from the following example:

importjavax.swing.*;
importjava.awt.*;
importjava.net.URL;
importjava.util.Objects;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassRenderExample {
publicstaticvoidmain(String[] args) {
SwingUtilities.invokeLater(() -> {
SVGLoaderloader = newSVGLoader();
URLsvgUrl = RenderExample.class.getResource("path/to/image.svg");
SVGDocumentdocument = loader.load(Objects.requireNonNull(svgUrl, "SVG file not found"));
JFrameframe = newJFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setPreferredSize(newDimension(400, 400));
frame.setContentPane(newSVGPanel(document));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
staticclassSVGPanelextendsJPanel {
privatefinal@NotNullSVGDocumentdocument;
SVGPanel(@NotNullSVGDocumentdocument) {
this.document = document;
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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, newViewBox(0, 0, getWidth(), getHeight()));
}
}
}

JavaFX

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

Required dependency: com.github.weisj:jsvg-javafx:2.0.1 (JavaFX 17 or later). See JavaFX renderer for the full dependency declaration.

The main entry point is FXSVGCanvas, a standard JavaFX Control that can be placed anywhere in a scene graph:

importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.ui.jfx.FXSVGCanvas;
importjavafx.application.Application;
importjavafx.scene.Scene;
importjavafx.scene.layout.StackPane;
importjavafx.stage.Stage;
publicclassFXRenderExampleextendsApplication {
@Overridepublicvoidstart(Stagestage) {
SVGLoaderloader = newSVGLoader();
SVGDocumentdocument = loader.load(getClass().getResource("path/to/image.svg"));
FXSVGCanvascanvas = newFXSVGCanvas();
// Choose the rendering backend:// RenderBackend.JavaFX - renders directly to a GraphicsContext (faster, hardware accelerated,// but some advanced features such as filters and masks may not render correctly)// RenderBackend.AWT - renders via the JSVG AWT pipeline (slower, but more accurate)canvas.setRenderBackend(FXSVGCanvas.RenderBackend.JavaFX);
canvas.setDocument(document);
stage.setScene(newScene(newStackPane(canvas), 400, 300));
stage.show();
}
publicstaticvoidmain(String[] args) {
launch(args);
}
}

FXSVGCanvas exposes JavaFX properties so it integrates naturally with bindings:

// Bind the document property to an external observablecanvas.documentProperty().bind(currentDocumentProperty);
// Show or hide the transparency checker-board pattern behind the SVGcanvas.setShowTransparentPattern(true);
// Places the svg viewport inside this region within the SVG canvas.canvas.setViewBox(newViewBox(0, 0, 200, 200));

Animations are driven automatically when animated is true (the default). You can also control playback manually:

canvas.pauseAnimation();
canvas.playAnimation();
canvas.restartAnimation();
// Disable automatic animation entirelycanvas.setAnimated(false);

For a more complete working example see FXTestViewerApplication in the test sources.

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:

<svgxmlns="http://www.w3.org/2000/svg"width="100"height="100"viewBox="0 0 100 100">
<rectx="0"y="0"width="100%"height="40%"id="myRect"></rect>
<rectx="0"y="60"width="100%"height="40%"></rect>
</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.

CustomColorsProcessorprocessor = newCustomColorsProcessor(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:

classCustomColorsProcessorimplementsDomProcessor {
privatefinalMap<String, DynamicAWTSvgPaint> customColors = newHashMap<>();
publicCustomColorsProcessor(@NotNullList<String> elementIds) {
for (StringelementId : elementIds) {
customColors.put(elementId, newDynamicAWTSvgPaint(Color.BLACK));
}
}
@NullableDynamicAWTSvgPaintcustomColorForId(@NotNullStringid) {
returncustomColors.get(id);
}
@Overridepublicvoidprocess(@NotNullDomElementroot) {
processImpl(root);
root.children().forEach(this::process);
}
privatevoidprocessImpl(@NotNullDomElementelement) {
// Obtain the id of the element.// Note: 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.StringnodeId = element.id();
if (customColors.containsKey(nodeId)) {
DynamicAWTSvgPaintdynamicColor = customColors.get(nodeId);
// This assumes the fill attribute is a plain color, not a gradient or pattern.Colorcolor = element.document().loaderContext().paintParser()
.parseColor(element.attribute("fill", "black"));
if (color == null) color = Color.BLACK;
dynamicColor.setColor(color);
// The id must be unique.StringuniqueIdForDynamicColor = UUID.randomUUID().toString();
// Register the dynamic color as a custom elementelement.document().registerNamedElement(uniqueIdForDynamicColor, dynamicColor);
// Refer to the custom element as the fill attributeelement.setAttribute("fill", uniqueIdForDynamicColor);
}
}
}
classDynamicAWTSvgPaintimplementsSimplePaintSVGPaint {
private@NotNullColorcolor;
DynamicAWTSvgPaint(@NotNullColorcolor) {
this.color = color;
}
publicvoidsetColor(@NotNullColorcolor) {
this.color = color;
}
public@NotNullColorcolor() {
returncolor;
}
@Overridepublic@NotNullPaintpaint() {
returncolor;
}
}

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:

DynamicAWTSvgPaintdynamicColor = processor.customColorForId("myRect");
SVGPanelpanel = newSVGPanel(document);
JButtonbutton = newJButton("Change color");
button.addActionListener(e -> {
ColornewColor = JColorChooser.showDialog(panel, "Choose a color", dynamicColor.color());
if (newColor != null) {
dynamicColor.setColor(newColor);
// Make sure to repaint the panel to see the changespanel.repaint();
}
});
JPanelcontent = newJPanel(newBorderLayout());
content.add(panel, BorderLayout.CENTER);
content.add(button, BorderLayout.SOUTH);
frame.setContentPane(content);

Animations (Swing)

JSVG provides a helper class AnimationPlayer for implementing animations in Swing components. The following example demonstrates how to use the AnimationPlayer to animate an SVG document:

importjavax.swing.*;
importjava.awt.*;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.renderer.animation.AnimationState;
importcom.github.weisj.jsvg.ui.AnimationPlayer;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassAnimationPanelextendsJComponent {
privatefinal@NotNullSVGDocumentdocument;
privatefinal@NotNullAnimationPlayerplayer;
publicAnimationPanel(@NotNullSVGDocumentdocument) {
this.document = document;
this.player = newAnimationPlayer(e -> repaint());
player.setAnimation(document.animation());
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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.renderWithPlatform(
newAwtComponentPlatformSupport(this),
Output.createForGraphics((Graphics2D) g),
newViewBox(0, 0, getWidth(), getHeight()),
player.animationState());
}
publicvoidstartAnimation() {
player.start();
}
publicvoidstopAnimation() {
player.stop();
}
}

Using a custom XML parser

If you need more control over how the XML source is parsed you can e.g. use a custom XMLInputFactory.

publicclassCustomXMLInputimplementsXMLInput {
privatefinal@NotNullXMLInputFactoryfactory;
privatefinal@NotNullInputStreaminputStream;
privateCustomXMLInput(@NotNullXMLInputFactoryfactory, @NotNullInputStreaminputStream) {
this.factory = factory;
this.inputStream = inputStream;
}
@Overridepublic@NotNullXMLEventReadercreateReader() throwsXMLStreamException {
returnfactory.createXMLEventReader(inputStream);
}
}
XMLInputFactoryfactory = XMLInputFactory.newFactory();
// Set up the factory to your likingURLinputUrl = ...;
SVGLoaderloader = newSVGLoader();
try (InputStreaminputStream = inputUrl.openStream()) {
SVGDocumentdocument = loader.load(
newCustomXMLInput(factory, inputStream),
inputUrl,
LoaderContext.createDefault()
);
}

About

Java SVG renderer

Topics

Resources

Contributing

Stars

221 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Quality Gate StatusCode StyleCIMaven Central

"Buy Me A Coffee"

JSVG - A Java SVG implementation

The SVG logo rendered by JSVG
The SVG logo rendered using JSVG

JSVG 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). However it does already cover most use cases and already supports more features than 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.

Table of contents

Projects using JSVG

How to use

The library is available on maven central:

dependencies {
implementation("com.github.weisj:jsvg:2.1.0")
}

Also, nightly snapshot builds will be released to maven:

repositories {
maven {
url = uri("https://central.sonatype.com/repository/maven-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 class. It will produce an SVGDocument

SVGLoaderloader = newSVGLoader();
URLsvgUrl = MyClass.class.getResource("mySvgFile.svg");
SVGDocumentsvgDocument = loader.load(svgUrl);

If you need more control over the loading process you can pass a LoaderContext for configuration purposes.

SVGDocumentsvgDocument = 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.

Rendering

An SVGDocument can be rendered to any Graphics2D object you like e.g. a BufferedImage

FloatSizesize = svgDocument.size();
BufferedImageimage = newBufferedImage((int) size.width,(int) size.height);
Graphics2Dg = image.createGraphics();
svgDocument.render(null,g);
g.dispose();

or a swing component

classMyComponentextendsJComponent {
@OverrideprotectedvoidpaintComponent(Graphicsg) {
super.paintComponent(g);
svgDocument.render(this, (Graphics2D) g, newViewBox(0, 0, getWidth(), getHeight()));
}
}

For more in-depth examples see Usage examples below.

Rendering Quality

The rendering quality can be adjusted by setting the RenderingHints of the Graphics2D object. The following properties are recommended:

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:

// Will use the value of RenderingHints.KEY_ANTIALIASING by defaultg.setRenderingHint(SVGRenderingHints.KEY_IMAGE_ANTIALIASING, SVGRenderingHints.VALUE_IMAGE_ANTIALIASING_ON);

By default clipping with a <clipPath> element does not use soft-clipping (i.e. anti-aliasing along the edges of the clip shape). This can be enabled by setting

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:

KeyValuesDefaultDescription
KEY_IMAGE_ANTIALIASINGVALUE_IMAGE_ANTIALIAS_ON
VALUE_IMAGE_ANTIALIAS_OFF
Value of RenderingHints.KEY_ANTIALIASINGEnables anti-aliasing for images
KEY_SOFT_CLIPPINGVALUE_SOFT_CLIPPING_ON
VALUE_SOFT_CLIPPING_OFF
VALUE_SOFT_CLIPPING_OFFEnables soft (anti-aliased) clipping for clipPath
KEY_MASK_CLIP_RENDERINGVALUE_MASK_CLIP_RENDERING_FAST
VALUE_MASK_CLIP_RENDERING_ACCURACY
VALUE_MASK_CLIP_RENDERING_DEFAULT
VALUE_MASK_CLIP_RENDERING_DEFAULT = VALUE_MASK_CLIP_RENDERING_FASTChanges 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_IMAGEVALUE_USE_CACHE
VALUE_NO_CACHE
VALUE_USE_CACHEWhether 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 the Animations (Swing) and JavaFX usage examples below for details.

Additional modules

JavaFX renderer (experimental)

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

JSVG provides an optional JavaFX rendering module that allows SVG documents to be displayed inside a JavaFX application. It requires JavaFX 17 or later.

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-javafx:2.0.1")
}

See the JavaFX usage example for a full code sample.

Logging

By default JSVG uses java.util.logging (JUL) for internal diagnostics. Two optional adapter modules are provided so you can route JSVG log output through your own logging framework without any additional configuration code — simply add the desired module to the classpath/module-path and the adapter is picked up automatically via ServiceLoader.

SLF4J adapter

Routes JSVG log output through any SLF4J 2.x compatible backend (Logback, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-slf4j:2.0.1")
// also add your preferred SLF4J backend, e.g.:
runtimeOnly("ch.qos.logback:logback-classic:1.5.6")
}
System.Logger adapter

Routes JSVG log output through the Java 9+ System.Logger API, which in turn delegates to whatever logging backend has been installed for the JVM (JUL, Log4j 2, etc.):

dependencies {
implementation("com.github.weisj:jsvg:2.0.1")
implementation("com.github.weisj:jsvg-systemlogger:2.0.1")
}

Both adapters provide OSGi metadata and register themselves as LogManager service providers. Only one adapter should be present on the classpath at a time.

Supported features

For supported elements most of the attributes which apply to them are implemented.

  • ✅: The element is supported. Note that this doesn't mean that every attribute is supported.
  • ✅*: The element is supported, but won't have any effect (e.g. it's currently not possible to query the content of a <desc> element)
  • ☑️: The element is partially implemented and might not support most basic features of the element.
  • ❌: The element is currently not supported
  • ⚠️: The element is deprecated in the spec and has a low priority of getting implemented.
  • 🧪: The element is an experimental part of the svg 2.* spec. It may not fully behave as expected.

Shape and container elements

ElementStatus
a
circle
clipPath
defs
ellipse
foreignObject
g
image
line
marker
mask
path
polygon
polyline
rect
svg
symbol
use
view✅*

Paint server elements

ElementStatus
linearGradient
🧪meshgradient
🧪meshrow
🧪meshpatch
pattern
radialGradient
solidColor
stop

Text elements

ElementStatus
text
textPath
⚠️tref
tspan

Animation elements

ElementStatus
animate☑️
⚠️animateColor
animateMotion
animateTransform☑️
mpath
set
switch

Filter elements

ElementStatus
feBlend
feColorMatrix
feComponentTransfer
feComposite
feConvolveMatrix
feDiffuseLighting
feDisplacementMap
feDistantLight
feDropShadow
feFlood
feFuncA
feFuncB
feFuncG
feFuncR
feGaussianBlur
feImage
feMerge
feMergeNode
feMorphology
feOffset
fePointLight
feSpecularLighting
feSpotLight
feTile
feTurbulence
filter☑️

Font elements

ElementStatus
⚠️altGlyph
⚠️altGlyphDef
⚠️altGlyphItem
⚠️font
⚠️font-face
⚠️font-face-format
⚠️font-face-name
⚠️font-face-src
⚠️font-face-uri
⚠️glyph
⚠️glyphRef
⚠️hkern
⚠️missing-glyph
⚠️vkern

Other elements

ElementStatus
desc( ✅ )
title( ✅ )
metadata( ✅ )
color-profile
⚠️cursor
script
style☑️

Usage examples

Basic (Swing)

To render an SVG to a Swing component you can start from the following example:

importjavax.swing.*;
importjava.awt.*;
importjava.net.URL;
importjava.util.Objects;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassRenderExample {
publicstaticvoidmain(String[] args) {
SwingUtilities.invokeLater(() -> {
SVGLoaderloader = newSVGLoader();
URLsvgUrl = RenderExample.class.getResource("path/to/image.svg");
SVGDocumentdocument = loader.load(Objects.requireNonNull(svgUrl, "SVG file not found"));
JFrameframe = newJFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setPreferredSize(newDimension(400, 400));
frame.setContentPane(newSVGPanel(document));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
staticclassSVGPanelextendsJPanel {
privatefinal@NotNullSVGDocumentdocument;
SVGPanel(@NotNullSVGDocumentdocument) {
this.document = document;
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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, newViewBox(0, 0, getWidth(), getHeight()));
}
}
}

JavaFX

⚠️ Note: The JavaFX renderer is experimental and its API is subject to change in future releases.

Required dependency: com.github.weisj:jsvg-javafx:2.0.1 (JavaFX 17 or later). See JavaFX renderer for the full dependency declaration.

The main entry point is FXSVGCanvas, a standard JavaFX Control that can be placed anywhere in a scene graph:

importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.parser.SVGLoader;
importcom.github.weisj.jsvg.ui.jfx.FXSVGCanvas;
importjavafx.application.Application;
importjavafx.scene.Scene;
importjavafx.scene.layout.StackPane;
importjavafx.stage.Stage;
publicclassFXRenderExampleextendsApplication {
@Overridepublicvoidstart(Stagestage) {
SVGLoaderloader = newSVGLoader();
SVGDocumentdocument = loader.load(getClass().getResource("path/to/image.svg"));
FXSVGCanvascanvas = newFXSVGCanvas();
// Choose the rendering backend:// RenderBackend.JavaFX - renders directly to a GraphicsContext (faster, hardware accelerated,// but some advanced features such as filters and masks may not render correctly)// RenderBackend.AWT - renders via the JSVG AWT pipeline (slower, but more accurate)canvas.setRenderBackend(FXSVGCanvas.RenderBackend.JavaFX);
canvas.setDocument(document);
stage.setScene(newScene(newStackPane(canvas), 400, 300));
stage.show();
}
publicstaticvoidmain(String[] args) {
launch(args);
}
}

FXSVGCanvas exposes JavaFX properties so it integrates naturally with bindings:

// Bind the document property to an external observablecanvas.documentProperty().bind(currentDocumentProperty);
// Show or hide the transparency checker-board pattern behind the SVGcanvas.setShowTransparentPattern(true);
// Places the svg viewport inside this region within the SVG canvas.canvas.setViewBox(newViewBox(0, 0, 200, 200));

Animations are driven automatically when animated is true (the default). You can also control playback manually:

canvas.pauseAnimation();
canvas.playAnimation();
canvas.restartAnimation();
// Disable automatic animation entirelycanvas.setAnimated(false);

For a more complete working example see FXTestViewerApplication in the test sources.

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:

<svgxmlns="http://www.w3.org/2000/svg"width="100"height="100"viewBox="0 0 100 100">
<rectx="0"y="0"width="100%"height="40%"id="myRect"></rect>
<rectx="0"y="60"width="100%"height="40%"></rect>
</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.

CustomColorsProcessorprocessor = newCustomColorsProcessor(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:

classCustomColorsProcessorimplementsDomProcessor {
privatefinalMap<String, DynamicAWTSvgPaint> customColors = newHashMap<>();
publicCustomColorsProcessor(@NotNullList<String> elementIds) {
for (StringelementId : elementIds) {
customColors.put(elementId, newDynamicAWTSvgPaint(Color.BLACK));
}
}
@NullableDynamicAWTSvgPaintcustomColorForId(@NotNullStringid) {
returncustomColors.get(id);
}
@Overridepublicvoidprocess(@NotNullDomElementroot) {
processImpl(root);
root.children().forEach(this::process);
}
privatevoidprocessImpl(@NotNullDomElementelement) {
// Obtain the id of the element.// Note: 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.StringnodeId = element.id();
if (customColors.containsKey(nodeId)) {
DynamicAWTSvgPaintdynamicColor = customColors.get(nodeId);
// This assumes the fill attribute is a plain color, not a gradient or pattern.Colorcolor = element.document().loaderContext().paintParser()
.parseColor(element.attribute("fill", "black"));
if (color == null) color = Color.BLACK;
dynamicColor.setColor(color);
// The id must be unique.StringuniqueIdForDynamicColor = UUID.randomUUID().toString();
// Register the dynamic color as a custom elementelement.document().registerNamedElement(uniqueIdForDynamicColor, dynamicColor);
// Refer to the custom element as the fill attributeelement.setAttribute("fill", uniqueIdForDynamicColor);
}
}
}
classDynamicAWTSvgPaintimplementsSimplePaintSVGPaint {
private@NotNullColorcolor;
DynamicAWTSvgPaint(@NotNullColorcolor) {
this.color = color;
}
publicvoidsetColor(@NotNullColorcolor) {
this.color = color;
}
public@NotNullColorcolor() {
returncolor;
}
@Overridepublic@NotNullPaintpaint() {
returncolor;
}
}

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:

DynamicAWTSvgPaintdynamicColor = processor.customColorForId("myRect");
SVGPanelpanel = newSVGPanel(document);
JButtonbutton = newJButton("Change color");
button.addActionListener(e -> {
ColornewColor = JColorChooser.showDialog(panel, "Choose a color", dynamicColor.color());
if (newColor != null) {
dynamicColor.setColor(newColor);
// Make sure to repaint the panel to see the changespanel.repaint();
}
});
JPanelcontent = newJPanel(newBorderLayout());
content.add(panel, BorderLayout.CENTER);
content.add(button, BorderLayout.SOUTH);
frame.setContentPane(content);

Animations (Swing)

JSVG provides a helper class AnimationPlayer for implementing animations in Swing components. The following example demonstrates how to use the AnimationPlayer to animate an SVG document:

importjavax.swing.*;
importjava.awt.*;
importcom.github.weisj.jsvg.SVGDocument;
importcom.github.weisj.jsvg.renderer.animation.AnimationState;
importcom.github.weisj.jsvg.ui.AnimationPlayer;
importcom.github.weisj.jsvg.view.ViewBox;
importorg.jetbrains.annotations.NotNull;
publicclassAnimationPanelextendsJComponent {
privatefinal@NotNullSVGDocumentdocument;
privatefinal@NotNullAnimationPlayerplayer;
publicAnimationPanel(@NotNullSVGDocumentdocument) {
this.document = document;
this.player = newAnimationPlayer(e -> repaint());
player.setAnimation(document.animation());
}
@OverrideprotectedvoidpaintComponent(Graphicsg) {
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.renderWithPlatform(
newAwtComponentPlatformSupport(this),
Output.createForGraphics((Graphics2D) g),
newViewBox(0, 0, getWidth(), getHeight()),
player.animationState());
}
publicvoidstartAnimation() {
player.start();
}
publicvoidstopAnimation() {
player.stop();
}
}

Using a custom XML parser

If you need more control over how the XML source is parsed you can e.g. use a custom XMLInputFactory.

publicclassCustomXMLInputimplementsXMLInput {
privatefinal@NotNullXMLInputFactoryfactory;
privatefinal@NotNullInputStreaminputStream;
privateCustomXMLInput(@NotNullXMLInputFactoryfactory, @NotNullInputStreaminputStream) {
this.factory = factory;
this.inputStream = inputStream;
}
@Overridepublic@NotNullXMLEventReadercreateReader() throwsXMLStreamException {
returnfactory.createXMLEventReader(inputStream);
}
}
XMLInputFactoryfactory = XMLInputFactory.newFactory();
// Set up the factory to your likingURLinputUrl = ...;
SVGLoaderloader = newSVGLoader();
try (InputStreaminputStream = inputUrl.openStream()) {
SVGDocumentdocument = loader.load(
newCustomXMLInput(factory, inputStream),
inputUrl,
LoaderContext.createDefault()
);
}

About

Java SVG renderer

Topics

Resources

Contributing

Stars

221 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages