diff --git a/pom.xml b/pom.xml
index b84dfa8c..9842bdfa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,6 +25,11 @@
javafx-fxml
${javafx.version}
+
+ org.openjfx
+ javafx-web
+ ${javafx.version}
+
org.junit.jupiter
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 3e89b391..e97c674f 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,9 +1,9 @@
module org.example.petshopdesktop {
requires javafx.controls;
requires javafx.fxml;
+ requires javafx.web;
requires java.sql;
requires java.net.http;
- requires java.xml;
requires com.fasterxml.jackson.databind;
requires com.fasterxml.jackson.core;
requires com.fasterxml.jackson.annotation;
diff --git a/src/main/java/org/example/petshopdesktop/controllers/LoginController.java b/src/main/java/org/example/petshopdesktop/controllers/LoginController.java
index c007fdea..402dd9df 100644
--- a/src/main/java/org/example/petshopdesktop/controllers/LoginController.java
+++ b/src/main/java/org/example/petshopdesktop/controllers/LoginController.java
@@ -16,7 +16,7 @@ import org.example.petshopdesktop.api.dto.auth.LoginResponse;
import org.example.petshopdesktop.api.dto.auth.UserInfoResponse;
import org.example.petshopdesktop.auth.Role;
import org.example.petshopdesktop.auth.UserSession;
-import org.example.petshopdesktop.ui.SvgNodeLoader;
+import org.example.petshopdesktop.ui.SvgWebViewFactory;
import org.example.petshopdesktop.util.ActivityLogger;
public class LoginController {
@@ -36,7 +36,7 @@ public class LoginController {
@FXML
public void initialize() {
lblError.setText("");
- logoContainer.getChildren().setAll(SvgNodeLoader.loadSquare("/org/example/petshopdesktop/images/leons-pet-store-badge-text.svg", 132));
+ logoContainer.getChildren().setAll(SvgWebViewFactory.build("/org/example/petshopdesktop/images/leons-pet-store-badge-text.svg", 132));
}
@FXML
diff --git a/src/main/java/org/example/petshopdesktop/ui/SvgNodeLoader.java b/src/main/java/org/example/petshopdesktop/ui/SvgNodeLoader.java
deleted file mode 100644
index 262aab23..00000000
--- a/src/main/java/org/example/petshopdesktop/ui/SvgNodeLoader.java
+++ /dev/null
@@ -1,201 +0,0 @@
-package org.example.petshopdesktop.ui;
-
-import javafx.scene.Group;
-import javafx.scene.layout.Pane;
-import javafx.scene.paint.Paint;
-import javafx.scene.shape.Circle;
-import javafx.scene.shape.FillRule;
-import javafx.scene.shape.Rectangle;
-import javafx.scene.shape.Shape;
-import javafx.scene.shape.SVGPath;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
-
-import javax.xml.parsers.DocumentBuilderFactory;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-public final class SvgNodeLoader {
- private SvgNodeLoader() {
- }
-
- public static Pane loadSquare(String resourcePath, double size) {
- SvgDocument svg = readSvg(resourcePath);
- double inset = size * 0.08;
- double available = size - (inset * 2);
- double scale = Math.min(available / svg.width(), available / svg.height());
-
- Group graphic = svg.content();
- graphic.setScaleX(scale);
- graphic.setScaleY(scale);
- graphic.setLayoutX(inset);
- graphic.setLayoutY(inset);
-
- Pane pane = new Pane(graphic);
- pane.setMinSize(size, size);
- pane.setPrefSize(size, size);
- pane.setMaxSize(size, size);
- pane.setClip(new Rectangle(size, size));
- return pane;
- }
-
- private static SvgDocument readSvg(String resourcePath) {
- try (Reader reader = requireResource(resourcePath)) {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(false);
- Document document = factory.newDocumentBuilder().parse(new InputSource(reader));
- Element root = document.getDocumentElement();
- double[] viewBox = parseViewBox(root.getAttribute("viewBox"));
- Map> styles = parseStyles(root);
- Group content = new Group();
- appendChildren(root, content, styles);
- content.setTranslateX(-viewBox[0]);
- content.setTranslateY(-viewBox[1]);
- return new SvgDocument(content, viewBox[2], viewBox[3]);
- } catch (Exception e) {
- throw new IllegalStateException("Unable to load SVG: " + resourcePath, e);
- }
- }
-
- private static InputStreamReader requireResource(String resourcePath) {
- var stream = SvgNodeLoader.class.getResourceAsStream(resourcePath);
- if (stream == null) {
- throw new IllegalArgumentException("Resource not found: " + resourcePath);
- }
- return new InputStreamReader(stream, StandardCharsets.UTF_8);
- }
-
- private static double[] parseViewBox(String viewBox) {
- String[] parts = viewBox.trim().split("\\s+");
- if (parts.length != 4) {
- throw new IllegalArgumentException("Invalid viewBox: " + viewBox);
- }
- return new double[] {
- Double.parseDouble(parts[0]),
- Double.parseDouble(parts[1]),
- Double.parseDouble(parts[2]),
- Double.parseDouble(parts[3])
- };
- }
-
- private static Map> parseStyles(Element root) {
- Map> styles = new HashMap<>();
- NodeList styleNodes = root.getElementsByTagName("style");
- for (int i = 0; i < styleNodes.getLength(); i++) {
- String css = styleNodes.item(i).getTextContent();
- for (String rule : css.split("}")) {
- String trimmedRule = rule.trim();
- if (trimmedRule.isEmpty()) {
- continue;
- }
- String[] selectorAndBody = trimmedRule.split("\\{", 2);
- if (selectorAndBody.length != 2) {
- continue;
- }
- String selector = selectorAndBody[0].trim();
- if (!selector.startsWith(".")) {
- continue;
- }
- Map properties = new LinkedHashMap<>();
- for (String declaration : selectorAndBody[1].split(";")) {
- String trimmedDeclaration = declaration.trim();
- if (trimmedDeclaration.isEmpty()) {
- continue;
- }
- String[] keyValue = trimmedDeclaration.split(":", 2);
- if (keyValue.length == 2) {
- properties.put(keyValue[0].trim(), keyValue[1].trim());
- }
- }
- styles.put(selector.substring(1), properties);
- }
- }
- return styles;
- }
-
- private static void appendChildren(Element parent, Group target, Map> styles) {
- NodeList childNodes = parent.getChildNodes();
- for (int i = 0; i < childNodes.getLength(); i++) {
- org.w3c.dom.Node child = childNodes.item(i);
- if (!(child instanceof Element element)) {
- continue;
- }
- switch (element.getTagName()) {
- case "g" -> {
- Group group = new Group();
- appendChildren(element, group, styles);
- target.getChildren().add(group);
- }
- case "circle" -> target.getChildren().add(buildCircle(element, styles));
- case "path" -> target.getChildren().add(buildPath(element, styles));
- default -> {
- }
- }
- }
- }
-
- private static Circle buildCircle(Element element, Map> styles) {
- Circle circle = new Circle(
- Double.parseDouble(element.getAttribute("cx")),
- Double.parseDouble(element.getAttribute("cy")),
- Double.parseDouble(element.getAttribute("r"))
- );
- applyShapeStyle(circle, element, styles);
- return circle;
- }
-
- private static SVGPath buildPath(Element element, Map> styles) {
- SVGPath path = new SVGPath();
- path.setContent(element.getAttribute("d"));
- applyShapeStyle(path, element, styles);
- return path;
- }
-
- private static void applyShapeStyle(Shape shape, Element element, Map> styles) {
- Map styleMap = new LinkedHashMap<>();
- String className = element.getAttribute("class");
- if (!className.isBlank()) {
- for (String cssClass : className.trim().split("\\s+")) {
- Map classStyles = styles.get(cssClass);
- if (classStyles != null) {
- styleMap.putAll(classStyles);
- }
- }
- }
- copyAttribute(element, styleMap, "fill");
- copyAttribute(element, styleMap, "stroke");
- copyAttribute(element, styleMap, "stroke-width");
- copyAttribute(element, styleMap, "fill-rule");
-
- if (styleMap.containsKey("fill") && !"none".equalsIgnoreCase(styleMap.get("fill"))) {
- shape.setFill(Paint.valueOf(styleMap.get("fill")));
- } else {
- shape.setFill(null);
- }
- if (styleMap.containsKey("stroke") && !"none".equalsIgnoreCase(styleMap.get("stroke"))) {
- shape.setStroke(Paint.valueOf(styleMap.get("stroke")));
- }
- if (styleMap.containsKey("stroke-width")) {
- shape.setStrokeWidth(Double.parseDouble(styleMap.get("stroke-width")));
- }
- if (styleMap.containsKey("fill-rule") && shape instanceof SVGPath svgPath) {
- svgPath.setFillRule("evenodd".equalsIgnoreCase(styleMap.get("fill-rule")) ? FillRule.EVEN_ODD : FillRule.NON_ZERO);
- }
- }
-
- private static void copyAttribute(Element element, Map styleMap, String attribute) {
- String value = element.getAttribute(attribute);
- if (!value.isBlank()) {
- styleMap.put(attribute, value);
- }
- }
-
- private record SvgDocument(Group content, double width, double height) {
- }
-}
diff --git a/src/main/java/org/example/petshopdesktop/ui/SvgWebViewFactory.java b/src/main/java/org/example/petshopdesktop/ui/SvgWebViewFactory.java
new file mode 100644
index 00000000..5b3ed1dc
--- /dev/null
+++ b/src/main/java/org/example/petshopdesktop/ui/SvgWebViewFactory.java
@@ -0,0 +1,48 @@
+package org.example.petshopdesktop.ui;
+
+import javafx.scene.layout.StackPane;
+import javafx.scene.web.WebView;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+public final class SvgWebViewFactory {
+ private SvgWebViewFactory() {
+ }
+
+ public static StackPane build(String resourcePath, double size) {
+ WebView webView = new WebView();
+ webView.setContextMenuEnabled(false);
+ webView.setPrefSize(size, size);
+ webView.setMinSize(size, size);
+ webView.setMaxSize(size, size);
+ webView.setZoom(1.0);
+ webView.setStyle("-fx-background-color: transparent;");
+
+ String svg = loadSvg(resourcePath)
+ .replaceFirst("