diff --git a/assets/README.md b/assets/README.md
index d77f68b..91f8eef 100644
--- a/assets/README.md
+++ b/assets/README.md
@@ -1,6 +1,6 @@
-# Pasta de Ícones (SVG) e Imagens (PNG) — CyberCore Signer
+# Recursos Visuais (Ícones e Imagens em SVG) — CyberCore Signer
-Esta pasta é o repositório oficial onde você pode visualizar, customizar e substituir livremente os **ícones em SVG** e as **imagens em PNG** do **CyberCore Signer**.
+Esta pasta é o repositório oficial onde você pode visualizar, customizar e substituir livremente todos os **ícones em SVG** e as **imagens em SVG** do **CyberCore Signer**.
---
@@ -31,13 +31,23 @@ assets/
│ ├── seal.svg # Ícone Designer e Gerenciador de Selos Notariais
│ ├── check.svg # Ícone Checkmark de sucesso e validação
│ ├── error.svg # Ícone de Erro / Alerta em vermelho
-│ └── star.svg # Ícone de Estrela notarial / favorita
+│ ├── star.svg # Ícone de Estrela notarial / favorita
+│ ├── close.svg # Ícone Fechar / Cancelar (cruz geométrica moderna)
+│ ├── arrow-right.svg # Ícone Avançar / Seta direcional
+│ ├── zoom-in.svg # Ícone Zoom In (+)
+│ ├── zoom-out.svg # Ícone Zoom Out (-)
+│ ├── zoom-fit.svg # Ícone Ajustar à Tela
+│ └── refresh.svg # Ícone Atualizar / Recarregar listas e status
│
-└── images/ # Imagens de alta fidelidade em PNG com canal alfa (transparência)
- ├── app-icon.png # Ícone oficial do app (256x256 px - Diamante 3D azul Serpro)
- ├── app-logo.png # Logotipo horizontal oficial (480x120 px - CyberCore SIGNER)
- ├── brand-banner.png # Banner de cabeçalho e marca (600x160 px - Serpro Blue)
- └── default-seal.png # Selo de assinatura padrão ICP-Brasil (640x164 px)
+└── images/ # Imagens vetoriais SVG editáveis (com cópias PNG de fallback)
+ ├── app-icon.svg # Ícone oficial do app em SVG (Cristal/Diamante 3D azul)
+ ├── app-icon.png # Fallback em PNG (256x256 px)
+ ├── app-logo.svg # Logotipo horizontal oficial em SVG (CyberCore SIGNER)
+ ├── app-logo.png # Fallback em PNG (480x120 px)
+ ├── brand-banner.svg # Banner de cabeçalho e marca em SVG (600x160 px)
+ ├── brand-banner.png # Fallback em PNG (600x160 px)
+ ├── default-seal.svg # Selo de assinatura padrão ICP-Brasil em SVG (640x164 px)
+ └── default-seal.png # Fallback em PNG (640x164 px)
```
---
@@ -45,20 +55,21 @@ assets/
## 🎨 Como Customizar Qualquer Ícone ou Imagem
1. **Ícones:**
- - Estão no formato `.svg` padrão.
- - Podem ser editados diretamente no seu editor de código (VS Code, etc.) ou em editores vetoriais como **Inkscape, Figma ou Illustrator**.
- - Se preferir colocar uma imagem bitmap para um ícone específico, você também pode colocar um `.png` (ex: `pdf.png`). O sistema prioriza `.svg` e aceita `.png` com fallback transparente.
+ - Todos estão no formato `.svg` padrão da W3C.
+ - Podem ser abertos e editados diretamente em editores de texto/código (VS Code) ou ferramentas vetoriais como **Inkscape, Figma, Adobe Illustrator**.
+ - Se preferir usar um arquivo bitmap para algum ícone específico, basta salvar um `.png` correspondente na pasta (ex: `pdf.png`). O sistema detecta e renderiza automaticamente.
2. **Imagens do Sistema:**
- - Estão no formato `.png` com canal alfa de 32 bits (fundo transparente).
- - Você pode substituir `app-logo.png`, `app-icon.png`, `brand-banner.png` ou `default-seal.png` pelo logotipo e identidade visual da sua instituição ou empresa.
+ - Tanto o logotipo horizontal (`app-logo.svg`), o ícone do sistema (`app-icon.svg`), o banner institucional (`brand-banner.svg`) e o selo notarial padrão (`default-seal.svg`) agora são arquivos **SVG vetoriais editáveis**.
+ - Você pode alterar cores, tipografias, brasões e dimensões diretamente nos arquivos SVG.
+ - O `AssetManager` renderiza os arquivos SVG preservando fidelidade geométrica e suportando gradientes e alinhamentos.
-3. **Ordem de Prioridade e Hot-Reloading:**
- Quando a aplicação busca um ícone ou imagem, a resolução ocorre na ordem:
- 1. **Pasta Física Local (`./assets/icons/` e `./assets/images/`)**: Qualquer alteração feita aqui entra em vigor imediatamente ao abrir a janela ou reiniciar a aplicação, sem necessidade de recompilar!
- 2. **Pasta do Usuário (`~/.cybercore/assets/`)**: Configurações específicas por usuário do sistema operacional.
- 3. **Resources Empacotados no JAR (`/assets/`)**: Cópia empacotada internamente no `.jar`.
- 4. **Fallback Vetorial Nativo (`SvgIcons`)**: Garantia de que a interface nunca fique em branco caso algum arquivo seja excluído.
+3. **Ordem de Resolução e Carregamento:**
+ Ao requisitar qualquer elemento gráfico, a aplicação segue a ordem de prioridade:
+ 1. **Pasta Física Local (`./assets/icons/` e `./assets/images/`)**: Arquivos `.svg` editados aqui têm prioridade máxima e entram em vigor imediatamente sem necessidade de recompilar.
+ 2. **Pasta do Usuário (`~/.cybercore/assets/`)**: Para customizações no diretório pessoal do usuário.
+ 3. **Recursos Empacotados no JAR (`/assets/`)**: SVGs e imagens internas empacotadas no pacote de distribuição.
+ 4. **Fallback Vetorial Nativo (`SvgIcons`)**: Garantia de que a interface nunca fique desprovida de grafismos caso algum arquivo seja removido.
---
diff --git a/assets/icons/about.svg b/assets/icons/about.svg
index f7cc7f1..f6aac90 100644
--- a/assets/icons/about.svg
+++ b/assets/icons/about.svg
@@ -1 +1,6 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/arrow-right.svg b/assets/icons/arrow-right.svg
new file mode 100644
index 0000000..1db6d0c
--- /dev/null
+++ b/assets/icons/arrow-right.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/close.svg b/assets/icons/close.svg
new file mode 100644
index 0000000..3f6cc79
--- /dev/null
+++ b/assets/icons/close.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/moon.svg b/assets/icons/moon.svg
index fd47105..9eb6f1a 100644
--- a/assets/icons/moon.svg
+++ b/assets/icons/moon.svg
@@ -1,6 +1,6 @@
diff --git a/assets/icons/refresh.svg b/assets/icons/refresh.svg
new file mode 100644
index 0000000..8ee09d9
--- /dev/null
+++ b/assets/icons/refresh.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/settings.svg b/assets/icons/settings.svg
index e0f4042..0a8dbc2 100644
--- a/assets/icons/settings.svg
+++ b/assets/icons/settings.svg
@@ -1,8 +1,4 @@
diff --git a/assets/icons/zoom-fit.svg b/assets/icons/zoom-fit.svg
new file mode 100644
index 0000000..201271c
--- /dev/null
+++ b/assets/icons/zoom-fit.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/zoom-in.svg b/assets/icons/zoom-in.svg
new file mode 100644
index 0000000..6783075
--- /dev/null
+++ b/assets/icons/zoom-in.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/zoom-out.svg b/assets/icons/zoom-out.svg
new file mode 100644
index 0000000..3882ea9
--- /dev/null
+++ b/assets/icons/zoom-out.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/app-icon.svg b/assets/images/app-icon.svg
new file mode 100644
index 0000000..10d2692
--- /dev/null
+++ b/assets/images/app-icon.svg
@@ -0,0 +1,37 @@
+
diff --git a/assets/images/app-logo.svg b/assets/images/app-logo.svg
new file mode 100644
index 0000000..ed887e6
--- /dev/null
+++ b/assets/images/app-logo.svg
@@ -0,0 +1,41 @@
+
diff --git a/assets/images/brand-banner.svg b/assets/images/brand-banner.svg
new file mode 100644
index 0000000..d133d1c
--- /dev/null
+++ b/assets/images/brand-banner.svg
@@ -0,0 +1,32 @@
+
diff --git a/assets/images/default-seal.svg b/assets/images/default-seal.svg
new file mode 100644
index 0000000..4b7c026
--- /dev/null
+++ b/assets/images/default-seal.svg
@@ -0,0 +1,34 @@
+
diff --git a/docs/screenshots/01_layout_claro.png b/docs/screenshots/01_layout_claro.png
index 3e2c1e9..c251c80 100644
Binary files a/docs/screenshots/01_layout_claro.png and b/docs/screenshots/01_layout_claro.png differ
diff --git a/docs/screenshots/02_layout_escuro.png b/docs/screenshots/02_layout_escuro.png
index 583fa6e..446b484 100644
Binary files a/docs/screenshots/02_layout_escuro.png and b/docs/screenshots/02_layout_escuro.png differ
diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/assets/AssetManager.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/assets/AssetManager.java
index b4b1cc0..1fc8938 100644
--- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/assets/AssetManager.java
+++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/assets/AssetManager.java
@@ -5,8 +5,14 @@ import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
+import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
+import javafx.scene.paint.CycleMethod;
+import javafx.scene.paint.LinearGradient;
+import javafx.scene.paint.Paint;
+import javafx.scene.paint.RadialGradient;
+import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon;
@@ -17,7 +23,6 @@ import javafx.scene.shape.Shape;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
-import javafx.scene.transform.Scale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
@@ -26,19 +31,20 @@ import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
-import java.io.ByteArrayInputStream;
-import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
/**
* Gerenciador centralizado de recursos visuais (ícones e imagens).
- * Permite que o usuário customize ou substitua facilmente qualquer ícone (SVG/PNG) ou imagem (PNG/JPG)
+ * Permite que o usuário customize ou substitua facilmente qualquer ícone ou imagem (SVG/PNG)
* do CyberCore Signer simplesmente colocando os arquivos na pasta 'assets/'
* na raiz do projeto ou no diretório ~/.cybercore/assets/.
*/
@@ -121,7 +127,7 @@ public final class AssetManager {
}
/**
- * Retorna um Node de ícone customizado (SVG ou PNG) ou invoca o fallback caso não exista imagem customizada.
+ * Retorna um Node de ícone customizado (prioritariamente SVG, com fallback para PNG ou supplier vetorial).
*/
public static Node getIconOrDefault(String iconName, double size, Supplier fallbackSupplier) {
if (iconName != null && !iconName.isBlank()) {
@@ -140,7 +146,20 @@ public final class AssetManager {
return userSvg.get();
}
- // 3. Se não houver SVG, tenta imagem bitmap (.png, .jpg) nas pastas externas
+ // 3. Procura SVG no Classpath interno
+ String resSvgPath = "/assets/icons/" + cleanName + ".svg";
+ InputStream resStream = AssetManager.class.getResourceAsStream(resSvgPath);
+ if (resStream != null) {
+ try (resStream) {
+ Node node = parseSvgToNode(resStream, size, size);
+ if (node != null) {
+ return node;
+ }
+ } catch (Exception ignored) {
+ }
+ }
+
+ // 4. Se não houver SVG, tenta imagem bitmap (.png, .jpg)
Optional customImg = loadImage("icons", cleanName, size, size);
if (customImg.isPresent() && !customImg.get().isError()) {
ImageView iv = new ImageView(customImg.get());
@@ -155,28 +174,76 @@ public final class AssetManager {
}
/**
- * Retorna a imagem do logotipo do sistema ou o fallback vetorial.
+ * Retorna uma imagem institucional ou elemento gráfico (prioritariamente SVG, com fallback para PNG ou supplier).
*/
- public static Node getLogoOrDefault(double width, double height, Supplier fallbackSupplier) {
- // Tenta carregar app-logo bitmap (.png) primeiro
- Optional customLogo = loadImage("images", "app-logo", width, height);
- if (customLogo.isPresent() && !customLogo.get().isError()) {
- ImageView iv = new ImageView(customLogo.get());
- iv.setFitWidth(width);
- iv.setFitHeight(height);
- iv.setPreserveRatio(true);
- return iv;
- }
+ public static Node getImageOrDefault(String imageName, double width, double height, Supplier fallbackSupplier) {
+ if (imageName != null && !imageName.isBlank()) {
+ String cleanName = cleanBaseName(imageName);
- // Tenta carregar SVG de app-logo se existir
- Optional svgLogo = loadSvgIconFromPath(Path.of("assets", "images", "app-logo.svg"), height);
- if (svgLogo.isPresent()) {
- return svgLogo.get();
+ // 1. Prioridade para SVG em ./assets/images/
+ Path localSvg = Path.of("assets", "images", cleanName + ".svg");
+ if (Files.isRegularFile(localSvg)) {
+ try (InputStream in = new FileInputStream(localSvg.toFile())) {
+ Node node = parseSvgToNode(in, width, height);
+ if (node != null) {
+ logger.debug("Carregada imagem SVG local: {}", localSvg);
+ return node;
+ }
+ } catch (Exception e) {
+ logger.warn("Falha ao ler imagem SVG local {}: {}", localSvg, e.getMessage());
+ }
+ }
+
+ // 2. Procura SVG em ~/.cybercore/assets/images/
+ String userHome = System.getProperty("user.home", ".");
+ Path userSvg = Path.of(userHome, ".cybercore", "assets", "images", cleanName + ".svg");
+ if (Files.isRegularFile(userSvg)) {
+ try (InputStream in = new FileInputStream(userSvg.toFile())) {
+ Node node = parseSvgToNode(in, width, height);
+ if (node != null) {
+ logger.debug("Carregada imagem SVG do usuário: {}", userSvg);
+ return node;
+ }
+ } catch (Exception e) {
+ logger.warn("Falha ao ler imagem SVG do usuário {}: {}", userSvg, e.getMessage());
+ }
+ }
+
+ // 3. Procura SVG no Classpath interno
+ String resPath = "/assets/images/" + cleanName + ".svg";
+ InputStream resStream = AssetManager.class.getResourceAsStream(resPath);
+ if (resStream != null) {
+ try (resStream) {
+ Node node = parseSvgToNode(resStream, width, height);
+ if (node != null) {
+ return node;
+ }
+ } catch (Exception e) {
+ logger.warn("Falha ao ler recurso interno SVG {}: {}", resPath, e.getMessage());
+ }
+ }
+
+ // 4. Fallback para imagem bitmap (.png, .jpg)
+ Optional customImg = loadImage("images", cleanName, width, height);
+ if (customImg.isPresent() && !customImg.get().isError()) {
+ ImageView iv = new ImageView(customImg.get());
+ iv.setFitWidth(width);
+ iv.setFitHeight(height);
+ iv.setPreserveRatio(true);
+ return iv;
+ }
}
return fallbackSupplier != null ? fallbackSupplier.get() : new ImageView();
}
+ /**
+ * Retorna a imagem do logotipo do sistema (SVG ou bitmap).
+ */
+ public static Node getLogoOrDefault(double width, double height, Supplier fallbackSupplier) {
+ return getImageOrDefault("app-logo", width, height, fallbackSupplier);
+ }
+
private static String cleanBaseName(String name) {
String clean = name;
int dotIdx = clean.lastIndexOf('.');
@@ -189,7 +256,7 @@ public final class AssetManager {
private static Optional loadSvgIconFromPath(Path path, double size) {
if (Files.isRegularFile(path)) {
try (InputStream in = new FileInputStream(path.toFile())) {
- Node node = parseSvgToNode(in, size);
+ Node node = parseSvgToNode(in, size, size);
if (node != null) {
logger.debug("Carregado ícone SVG customizado de: {}", path);
return Optional.of(node);
@@ -202,9 +269,16 @@ public final class AssetManager {
}
/**
- * Parser analítico leve de SVG padrão para nós gráficos JavaFX com preservação geométrica exata.
+ * Parser analítico de SVG para nós gráficos JavaFX com suporte a dimensões quadradas.
*/
public static Node parseSvgToNode(InputStream in, double targetSize) {
+ return parseSvgToNode(in, targetSize, targetSize);
+ }
+
+ /**
+ * Parser analítico completo de SVG para nós gráficos JavaFX com dimensões proporcionais e suporte a defs/gradientes.
+ */
+ public static Node parseSvgToNode(InputStream in, double targetWidth, double targetHeight) {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(false);
@@ -249,27 +323,34 @@ public final class AssetManager {
if (vbWidth <= 0) vbWidth = 48;
if (vbHeight <= 0) vbHeight = 48;
- Group contentGroup = new Group();
- renderChildren(svg, contentGroup);
+ Map defsMap = new HashMap<>();
+ parseDefs(svg, defsMap);
- double maxDim = Math.max(vbWidth, vbHeight);
- double scale = (targetSize * 0.90) / maxDim;
+ Group contentGroup = new Group();
+ renderChildren(svg, contentGroup, defsMap);
+
+ double scaleX = targetWidth / vbWidth;
+ double scaleY = targetHeight / vbHeight;
+ double scale = Math.min(scaleX, scaleY);
+ if (scale <= 0) scale = 1.0;
double drawnW = vbWidth * scale;
double drawnH = vbHeight * scale;
- double tx = (targetSize - drawnW) / 2.0 - (vbMinX * scale);
- double ty = (targetSize - drawnH) / 2.0 - (vbMinY * scale);
+ double tx = (targetWidth - drawnW) / 2.0 - (vbMinX * scale);
+ double ty = (targetHeight - drawnH) / 2.0 - (vbMinY * scale);
+ contentGroup.setManaged(false);
+ contentGroup.setLayoutX(0);
+ contentGroup.setLayoutY(0);
contentGroup.getTransforms().setAll(
new javafx.scene.transform.Translate(tx, ty),
new javafx.scene.transform.Scale(scale, scale, 0, 0)
);
- StackPane container = new StackPane(contentGroup);
- container.setPrefSize(targetSize, targetSize);
- container.setMaxSize(targetSize, targetSize);
- container.setMinSize(targetSize, targetSize);
- container.setAlignment(Pos.CENTER);
+ Pane container = new Pane(contentGroup);
+ container.setPrefSize(targetWidth, targetHeight);
+ container.setMaxSize(targetWidth, targetHeight);
+ container.setMinSize(targetWidth, targetHeight);
return container;
} catch (Exception e) {
@@ -278,12 +359,100 @@ public final class AssetManager {
}
}
- private static void renderChildren(Element parent, Group targetGroup) {
+ private static void parseDefs(Element root, Map defsMap) {
+ NodeList defsList = root.getElementsByTagName("defs");
+ for (int i = 0; i < defsList.getLength(); i++) {
+ org.w3c.dom.Node defNode = defsList.item(i);
+ if (defNode instanceof Element defsEl) {
+ NodeList children = defsEl.getChildNodes();
+ for (int j = 0; j < children.getLength(); j++) {
+ org.w3c.dom.Node c = children.item(j);
+ if (c instanceof Element gradEl) {
+ String id = gradEl.getAttribute("id");
+ if (!id.isBlank()) {
+ Paint paint = createPaintFromGradient(gradEl);
+ if (paint != null) {
+ defsMap.put(id, paint);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static Paint createPaintFromGradient(Element gradEl) {
+ String tag = gradEl.getTagName().toLowerCase();
+ List stops = new ArrayList<>();
+ NodeList stopNodes = gradEl.getElementsByTagName("stop");
+ for (int i = 0; i < stopNodes.getLength(); i++) {
+ org.w3c.dom.Node s = stopNodes.item(i);
+ if (s instanceof Element stopEl) {
+ double offset = parseOffset(stopEl.getAttribute("offset"));
+ String colorStr = stopEl.getAttribute("stop-color");
+ double opacity = parseDouble(stopEl.getAttribute("stop-opacity"), 1.0);
+ Color baseColor = parseColor(colorStr, Color.BLACK);
+ Color finalColor = new Color(baseColor.getRed(), baseColor.getGreen(), baseColor.getBlue(), baseColor.getOpacity() * opacity);
+ stops.add(new Stop(offset, finalColor));
+ }
+ }
+
+ if (stops.isEmpty()) {
+ stops.add(new Stop(0, Color.web("#0288d1")));
+ stops.add(new Stop(1, Color.web("#01579b")));
+ }
+
+ if ("lineargradient".equals(tag)) {
+ double x1 = parseCoordinate(gradEl.getAttribute("x1"), 0);
+ double y1 = parseCoordinate(gradEl.getAttribute("y1"), 0);
+ double x2 = parseCoordinate(gradEl.getAttribute("x2"), 1);
+ double y2 = parseCoordinate(gradEl.getAttribute("y2"), 1);
+ return new LinearGradient(x1, y1, x2, y2, true, CycleMethod.NO_CYCLE, stops);
+ } else if ("radialgradient".equals(tag)) {
+ double cx = parseCoordinate(gradEl.getAttribute("cx"), 0.5);
+ double cy = parseCoordinate(gradEl.getAttribute("cy"), 0.5);
+ double r = parseCoordinate(gradEl.getAttribute("r"), 0.5);
+ return new RadialGradient(0, 0, cx, cy, r, true, CycleMethod.NO_CYCLE, stops);
+ }
+
+ return null;
+ }
+
+ private static double parseOffset(String str) {
+ if (str == null || str.isBlank()) return 0;
+ String s = str.trim();
+ try {
+ if (s.endsWith("%")) {
+ return Double.parseDouble(s.substring(0, s.length() - 1)) / 100.0;
+ }
+ return Double.parseDouble(s);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ private static double parseCoordinate(String str, double def) {
+ if (str == null || str.isBlank()) return def;
+ String s = str.trim();
+ try {
+ if (s.endsWith("%")) {
+ return Double.parseDouble(s.substring(0, s.length() - 1)) / 100.0;
+ }
+ return Double.parseDouble(s);
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
+
+ private static void renderChildren(Element parent, Group targetGroup, Map defsMap) {
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
org.w3c.dom.Node n = children.item(i);
if (n instanceof Element el) {
- Node jfxNode = createElementNode(el);
+ if ("defs".equalsIgnoreCase(el.getTagName())) {
+ continue;
+ }
+ Node jfxNode = createElementNode(el, defsMap);
if (jfxNode != null) {
targetGroup.getChildren().add(jfxNode);
}
@@ -291,7 +460,7 @@ public final class AssetManager {
}
}
- private static Node createElementNode(Element el) {
+ private static Node createElementNode(Element el, Map defsMap) {
String tagName = el.getTagName().toLowerCase();
Shape shape = null;
@@ -350,12 +519,12 @@ public final class AssetManager {
double x = parseDouble(el.getAttribute("x"), 0);
double y = parseDouble(el.getAttribute("y"), 0);
String content = el.getTextContent();
- Text t = new Text(x, y, content);
- double fs = parseDouble(el.getAttribute("font-size"), 10);
+ Text t = new Text(x, y, content != null ? content.trim() : "");
+ double fs = parseDouble(el.getAttribute("font-size"), 12);
String fw = el.getAttribute("font-weight");
t.setFont(Font.font("Segoe UI", "bold".equalsIgnoreCase(fw) ? FontWeight.BOLD : FontWeight.NORMAL, fs));
if (el.hasAttribute("fill")) {
- t.setFill(parseColor(el.getAttribute("fill"), Color.BLACK));
+ t.setFill(parsePaint(el.getAttribute("fill"), defsMap, Color.BLACK));
}
if ("middle".equalsIgnoreCase(el.getAttribute("text-anchor"))) {
t.setTextOrigin(javafx.geometry.VPos.CENTER);
@@ -364,38 +533,40 @@ public final class AssetManager {
}
case "g" -> {
Group g = new Group();
- renderChildren(el, g);
+ renderChildren(el, g, defsMap);
if (el.hasAttribute("stroke")) {
- Color strokeCol = parseColor(el.getAttribute("stroke"), null);
+ Paint strokePaint = parsePaint(el.getAttribute("stroke"), defsMap, null);
double sw = parseDouble(el.getAttribute("stroke-width"), 1.0);
for (Node child : g.getChildren()) {
if (child instanceof Shape s) {
- if (strokeCol != null) s.setStroke(strokeCol);
+ if (strokePaint != null) s.setStroke(strokePaint);
s.setStrokeWidth(sw);
}
}
}
+ applyElementTransform(el, g);
return g;
}
}
if (shape != null) {
- applyShapeStyles(el, shape);
+ applyShapeStyles(el, shape, defsMap);
+ applyElementTransform(el, shape);
return shape;
}
return null;
}
- private static void applyShapeStyles(Element el, Shape shape) {
+ private static void applyShapeStyles(Element el, Shape shape, Map defsMap) {
if (el.hasAttribute("fill")) {
- shape.setFill(parseColor(el.getAttribute("fill"), Color.TRANSPARENT));
+ shape.setFill(parsePaint(el.getAttribute("fill"), defsMap, Color.TRANSPARENT));
} else if (shape instanceof Line) {
shape.setFill(Color.TRANSPARENT);
}
if (el.hasAttribute("stroke")) {
- shape.setStroke(parseColor(el.getAttribute("stroke"), Color.TRANSPARENT));
+ shape.setStroke(parsePaint(el.getAttribute("stroke"), defsMap, Color.TRANSPARENT));
}
if (el.hasAttribute("stroke-width")) {
@@ -409,6 +580,22 @@ public final class AssetManager {
}
}
+ private static Paint parsePaint(String str, Map defsMap, Paint fallback) {
+ if (str == null || str.isBlank() || "none".equalsIgnoreCase(str)) {
+ return Color.TRANSPARENT;
+ }
+ String trimmed = str.trim();
+ if (trimmed.startsWith("url(#") && trimmed.endsWith(")")) {
+ String id = trimmed.substring(5, trimmed.length() - 1).trim();
+ Paint defPaint = defsMap.get(id);
+ if (defPaint != null) {
+ return defPaint;
+ }
+ return Color.web("#0288d1");
+ }
+ return parseColor(trimmed, fallback instanceof Color ? (Color) fallback : Color.BLACK);
+ }
+
private static void parsePoints(String ptsStr, java.util.List pointsList) {
String[] tokens = ptsStr.trim().split("[,\\s]+");
for (String t : tokens) {
@@ -446,4 +633,52 @@ public final class AssetManager {
return defaultValue;
}
}
+
+ private static void applyElementTransform(Element el, Node node) {
+ if (!el.hasAttribute("transform")) return;
+ String tr = el.getAttribute("transform").trim();
+ if (tr.isBlank()) return;
+
+ try {
+ if (tr.contains("rotate(")) {
+ int start = tr.indexOf("rotate(") + 7;
+ int close = tr.indexOf(')', start);
+ if (close > 0) {
+ String[] args = tr.substring(start, close).trim().split("[,\\s]+");
+ if (args.length >= 3) {
+ double deg = parseDouble(args[0], 0);
+ double px = parseDouble(args[1], 0);
+ double py = parseDouble(args[2], 0);
+ node.getTransforms().add(new javafx.scene.transform.Rotate(deg, px, py));
+ } else if (args.length >= 1) {
+ double deg = parseDouble(args[0], 0);
+ node.getTransforms().add(new javafx.scene.transform.Rotate(deg));
+ }
+ }
+ }
+ if (tr.contains("translate(")) {
+ int start = tr.indexOf("translate(") + 10;
+ int close = tr.indexOf(')', start);
+ if (close > 0) {
+ String[] args = tr.substring(start, close).trim().split("[,\\s]+");
+ double tx = parseDouble(args[0], 0);
+ double ty = args.length > 1 ? parseDouble(args[1], 0) : 0;
+ node.getTransforms().add(new javafx.scene.transform.Translate(tx, ty));
+ }
+ }
+ if (tr.contains("scale(")) {
+ int start = tr.indexOf("scale(") + 6;
+ int close = tr.indexOf(')', start);
+ if (close > 0) {
+ String[] args = tr.substring(start, close).trim().split("[,\\s]+");
+ double sx = parseDouble(args[0], 1.0);
+ double sy = args.length > 1 ? parseDouble(args[1], sx) : sx;
+ node.getTransforms().add(new javafx.scene.transform.Scale(sx, sy));
+ }
+ }
+ } catch (Exception ignored) {
+ }
+ }
}
+
+
diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/components/SvgIcons.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/components/SvgIcons.java
index 12061e0..05779c1 100644
--- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/components/SvgIcons.java
+++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/components/SvgIcons.java
@@ -709,6 +709,10 @@ public final class SvgIcons {
}
public static Node closeIcon(double size) {
+ return AssetManager.getIconOrDefault("close", size, () -> buildCloseIcon(size));
+ }
+
+ public static Node buildCloseIcon(double size) {
StackPane box = new StackPane();
box.setPrefSize(size, size);
box.setMaxSize(size, size);
@@ -738,6 +742,10 @@ public final class SvgIcons {
}
public static Node arrowRightIcon(double size, String colorHex) {
+ return AssetManager.getIconOrDefault("arrow-right", size, () -> buildArrowRightIcon(size, colorHex));
+ }
+
+ public static Node buildArrowRightIcon(double size, String colorHex) {
StackPane box = new StackPane();
box.setPrefSize(size, size);
box.setMaxSize(size, size);
@@ -764,6 +772,10 @@ public final class SvgIcons {
* Ícones vetoriais de Zoom In (+), Zoom Out (-) e Ajustar à Tela.
*/
public static Node zoomInIcon(double size) {
+ return AssetManager.getIconOrDefault("zoom-in", size, () -> buildZoomInIcon(size));
+ }
+
+ public static Node buildZoomInIcon(double size) {
StackPane box = new StackPane();
box.setPrefSize(size, size);
box.setMaxSize(size, size);
@@ -786,6 +798,10 @@ public final class SvgIcons {
}
public static Node zoomOutIcon(double size) {
+ return AssetManager.getIconOrDefault("zoom-out", size, () -> buildZoomOutIcon(size));
+ }
+
+ public static Node buildZoomOutIcon(double size) {
StackPane box = new StackPane();
box.setPrefSize(size, size);
box.setMaxSize(size, size);
@@ -808,6 +824,10 @@ public final class SvgIcons {
}
public static Node zoomFitIcon(double size) {
+ return AssetManager.getIconOrDefault("zoom-fit", size, () -> buildZoomFitIcon(size));
+ }
+
+ public static Node buildZoomFitIcon(double size) {
StackPane box = new StackPane();
box.setPrefSize(size, size);
box.setMaxSize(size, size);
@@ -828,4 +848,33 @@ public final class SvgIcons {
box.getChildren().add(path);
return box;
}
+
+ /**
+ * Ícone de Atualização / Sincronização.
+ */
+ public static Node refreshIcon(double size) {
+ return AssetManager.getIconOrDefault("refresh", size, () -> buildRefreshIcon(size));
+ }
+
+ public static Node buildRefreshIcon(double size) {
+ StackPane box = new StackPane();
+ box.setPrefSize(size, size);
+ box.setMaxSize(size, size);
+ box.setMinSize(size, size);
+ box.setAlignment(Pos.CENTER);
+
+ SVGPath path = new SVGPath();
+ path.setContent("M 20 4 L 20 9 L 15 9 M 4 20 L 4 15 L 9 15 M 5.8 9 A 7 7 0 0 1 18.5 7.5 L 20 9 M 4 15 L 5.5 16.5 A 7 7 0 0 0 18.2 15");
+ path.setStroke(Color.web("#0284c7"));
+ path.setStrokeWidth(1.8);
+ path.setStrokeLineCap(javafx.scene.shape.StrokeLineCap.ROUND);
+ path.setFill(Color.TRANSPARENT);
+
+ double scale = size / 24.0;
+ path.setScaleX(scale);
+ path.setScaleY(scale);
+
+ box.getChildren().add(path);
+ return box;
+ }
}
diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/AboutView.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/AboutView.java
index d9ece1c..c46360c 100644
--- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/AboutView.java
+++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/AboutView.java
@@ -1,12 +1,15 @@
package br.com.cybercore.signer.ui.view;
import br.com.cybercore.signer.ui.CyberCoreUiConstants;
+import br.com.cybercore.signer.ui.assets.AssetManager;
+import br.com.cybercore.signer.ui.components.SvgIcons;
import javafx.geometry.Pos;
+import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
/**
- * Painel informativo Sobre o CyberCore Signer.
+ * Painel informativo Sobre o CyberCore Signer com exibição de identidade visual SVG e dados do agente.
*/
@SuppressWarnings("this-escape")
public final class AboutView extends VBox {
@@ -16,13 +19,12 @@ public final class AboutView extends VBox {
}
private void initView() {
- setSpacing(16);
+ setSpacing(14);
setAlignment(Pos.CENTER);
getStyleClass().add("content-pane");
- Label brandLabel = new Label(CyberCoreUiConstants.APP_NAME);
- brandLabel.getStyleClass().add("brand-title");
- brandLabel.setStyle("-fx-font-size: 24px;");
+ // Logotipo oficial vetorial SVG (app-logo.svg) com fallback para cristal 3D
+ Node logoNode = AssetManager.getLogoOrDefault(320, 80, () -> SvgIcons.createAppCrystalLogo(54));
Label versionLabel = new Label("Versão " + CyberCoreUiConstants.APP_VERSION + " • CCSP/1");
versionLabel.getStyleClass().add("status-badge-active");
@@ -44,9 +46,9 @@ public final class AboutView extends VBox {
detailsBox.getChildren().add(lbl);
}
- Label copyLabel = new Label("© 2026 CyberCore. Todos os direitos reservados.");
+ Label copyLabel = new Label("© 2026 CyberCore Systems. Todos os direitos reservados.");
copyLabel.getStyleClass().add("dialog-subtitle");
- getChildren().addAll(brandLabel, versionLabel, descLabel, detailsBox, copyLabel);
+ getChildren().addAll(logoNode, versionLabel, descLabel, detailsBox, copyLabel);
}
}
diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/ApplicationApprovalDialog.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/ApplicationApprovalDialog.java
index 092333d..151a495 100644
--- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/ApplicationApprovalDialog.java
+++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/ApplicationApprovalDialog.java
@@ -39,7 +39,10 @@ public class ApplicationApprovalDialog {
root.setPadding(new Insets(24));
root.getStyleClass().add("dialog-container");
- // Cabeçalho
+ // Cabeçalho com Ícone SVG de Escudo
+ HBox header = new HBox(14);
+ header.setAlignment(Pos.CENTER_LEFT);
+
VBox headerBox = new VBox(4);
Label titleLabel = new Label("NOVA APLICAÇÃO WEB SOLICITA ACESSO");
titleLabel.getStyleClass().add("dialog-header");
@@ -48,6 +51,8 @@ public class ApplicationApprovalDialog {
subtitleLabel.getStyleClass().add("dialog-subtitle");
headerBox.getChildren().addAll(titleLabel, subtitleLabel);
+ header.getChildren().addAll(br.com.cybercore.signer.ui.components.SvgIcons.cyberShieldLogo(34), headerBox);
+
// Painel de Detalhes da Aplicação
VBox detailsBox = new VBox(10);
detailsBox.getStyleClass().add("dialog-box");
@@ -106,7 +111,7 @@ public class ApplicationApprovalDialog {
actionsRow.getChildren().addAll(btnReject, btnAuthorize);
- root.getChildren().addAll(headerBox, detailsBox, secWarning, actionsRow);
+ root.getChildren().addAll(header, detailsBox, secWarning, actionsRow);
Scene scene = new Scene(root);
ThemeManager.applyTheme(scene);
diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/CertificatesManagerDialog.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/CertificatesManagerDialog.java
index c568a96..c0968f1 100644
--- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/CertificatesManagerDialog.java
+++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/CertificatesManagerDialog.java
@@ -77,7 +77,7 @@ public final class CertificatesManagerDialog {
btnAddA1.getStyleClass().add("btn-primary");
btnAddA1.setOnAction(e -> handleImportA1());
- Button btnRefresh = new Button("Atualizar Lista");
+ Button btnRefresh = new Button(" Atualizar Lista", SvgIcons.refreshIcon(14));
btnRefresh.getStyleClass().add("btn-secondary");
btnRefresh.setOnAction(e -> refreshList());
@@ -93,7 +93,7 @@ public final class CertificatesManagerDialog {
HBox footer = new HBox();
footer.setAlignment(Pos.CENTER_RIGHT);
- Button btnClose = new Button("Fechar");
+ Button btnClose = new Button(" Fechar", SvgIcons.closeIcon(13));
btnClose.getStyleClass().add("btn-secondary");
btnClose.setOnAction(e -> stage.close());
footer.getChildren().add(btnClose);
diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/MainAppView.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/MainAppView.java
index 7bf672c..e6c4d4f 100644
--- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/MainAppView.java
+++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/MainAppView.java
@@ -13,6 +13,7 @@ import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
+import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
@@ -204,14 +205,20 @@ public final class MainAppView {
private void updateThemeToggleButton() {
boolean dark = ThemeManager.isDark();
- HBox content = new HBox(6);
+ HBox content = new HBox(8);
content.setAlignment(Pos.CENTER);
Node icon = dark ? SvgIcons.sunIcon(15) : SvgIcons.moonIcon(15);
+ StackPane iconBox = new StackPane(icon);
+ iconBox.setPrefSize(18, 18);
+ iconBox.setMinSize(18, 18);
+ iconBox.setMaxSize(18, 18);
+ iconBox.setAlignment(Pos.CENTER);
+
Label label = new Label(dark ? "Modo Claro" : "Modo Escuro");
label.setStyle("-fx-font-size: 11.5px; -fx-font-weight: bold;");
- content.getChildren().addAll(icon, label);
+ content.getChildren().addAll(iconBox, label);
themeToggleBtn.setGraphic(content);
}
@@ -223,16 +230,22 @@ public final class MainAppView {
btn.setPrefHeight(68);
btn.setMinHeight(68);
- HBox content = new HBox(10);
+ HBox content = new HBox(12);
content.setAlignment(Pos.CENTER_LEFT);
Label lbl = new Label(text);
lbl.getStyleClass().addAll("cyber-action-text", "serpro-action-text");
lbl.setWrapText(true);
+ lbl.setAlignment(Pos.CENTER_LEFT);
HBox.setHgrow(lbl, Priority.ALWAYS);
if (icon != null) {
- content.getChildren().addAll(icon, lbl);
+ StackPane iconSlot = new StackPane(icon);
+ iconSlot.setPrefSize(40, 40);
+ iconSlot.setMinSize(40, 40);
+ iconSlot.setMaxSize(40, 40);
+ iconSlot.setAlignment(Pos.CENTER);
+ content.getChildren().addAll(iconSlot, lbl);
} else {
content.getChildren().add(lbl);
}
@@ -251,16 +264,22 @@ public final class MainAppView {
btn.setMinHeight(42);
btn.setMaxWidth(Double.MAX_VALUE);
- HBox content = new HBox(10);
+ HBox content = new HBox(12);
content.setAlignment(Pos.CENTER_LEFT);
Label lbl = new Label(text);
lbl.getStyleClass().addAll("cyber-side-text", "serpro-side-text");
- lbl.setWrapText(true);
+ lbl.setWrapText(false);
+ lbl.setAlignment(Pos.CENTER_LEFT);
HBox.setHgrow(lbl, Priority.ALWAYS);
if (icon != null) {
- content.getChildren().addAll(icon, lbl);
+ StackPane iconSlot = new StackPane(icon);
+ iconSlot.setPrefSize(26, 26);
+ iconSlot.setMinSize(26, 26);
+ iconSlot.setMaxSize(26, 26);
+ iconSlot.setAlignment(Pos.CENTER);
+ content.getChildren().addAll(iconSlot, lbl);
} else {
content.getChildren().add(lbl);
}
diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfSignDialog.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfSignDialog.java
index c91b0db..07bee99 100644
--- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfSignDialog.java
+++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfSignDialog.java
@@ -488,7 +488,7 @@ public final class PdfSignDialog {
resizeToolBox.setAlignment(Pos.CENTER_LEFT);
resizeToolBox.setStyle("-fx-background-color: -fx-bg-primary; -fx-border-color: -fx-border-light; -fx-border-radius: 6px; -fx-padding: 2px 6px;");
- btnToggleResize = new Button("📏 Redimensionar");
+ btnToggleResize = new Button(" Redimensionar", SvgIcons.zoomFitIcon(13));
btnToggleResize.getStyleClass().addAll("btn-secondary", "btn-compact");
btnToggleResize.setTooltip(new Tooltip("Ativar/desativar alça interativa para alterar o tamanho do selo"));
btnToggleResize.setOnAction(e -> toggleResizeMode());
@@ -496,7 +496,7 @@ public final class PdfSignDialog {
lblSealScaleIndicator = new Label("100%");
lblSealScaleIndicator.setStyle("-fx-font-size: 11px; -fx-font-weight: bold; -fx-min-width: 36px; -fx-alignment: center;");
- btnResetSealScale = new Button("↺ 100%");
+ btnResetSealScale = new Button(" 100%", SvgIcons.refreshIcon(12));
btnResetSealScale.getStyleClass().addAll("btn-secondary", "btn-compact");
btnResetSealScale.setTooltip(new Tooltip("Restaurar tamanho padrão original (100%)"));
btnResetSealScale.setVisible(false);
@@ -507,7 +507,7 @@ public final class PdfSignDialog {
Region tbSpacer = new Region();
HBox.setHgrow(tbSpacer, Priority.ALWAYS);
- lblPositionFeedback = new Label("👉 Clique na folha para posicionar");
+ lblPositionFeedback = new Label("Clique na folha para posicionar");
lblPositionFeedback.setStyle("-fx-font-size: 11.5px; -fx-text-fill: -fx-accent; -fx-font-weight: bold;");
toolbar.getChildren().addAll(pageNavBox, zoomBox, resizeToolBox, tbSpacer, lblPositionFeedback);
diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/SignaturePromptDialog.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/SignaturePromptDialog.java
index e304a75..5c6dc79 100644
--- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/SignaturePromptDialog.java
+++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/SignaturePromptDialog.java
@@ -5,6 +5,7 @@ import br.com.cybercore.signer.core.domain.ManagedCertificate;
import br.com.cybercore.signer.core.domain.SigningContext;
import br.com.cybercore.signer.core.security.PinSessionManager;
import br.com.cybercore.signer.ui.CyberCoreUiConstants;
+import br.com.cybercore.signer.ui.components.SvgIcons;
import br.com.cybercore.signer.ui.theme.ThemeManager;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
@@ -60,13 +61,23 @@ public class SignaturePromptDialog {
root.getStyleClass().add("dialog-container");
root.setPrefWidth(540);
- Label header = new Label(isAuthentication ? "🔑 SOLICITAÇÃO DE LOGIN DIGITAL" : "SOLICITAÇÃO DE ASSINATURA");
+ HBox headerBox = new HBox(12);
+ headerBox.setAlignment(Pos.CENTER_LEFT);
+
+ VBox titleBox = new VBox(2);
+ Label header = new Label(isAuthentication ? "SOLICITAÇÃO DE LOGIN DIGITAL" : "SOLICITAÇÃO DE ASSINATURA");
header.getStyleClass().add("dialog-header");
Label desc = new Label(isAuthentication
? "Uma aplicação web está solicitando autenticação de identidade com seu Certificado Digital ICP-Brasil."
: "Uma aplicação web está solicitando a assinatura digital de um documento.");
desc.getStyleClass().add("brand-subtitle");
+ titleBox.getChildren().addAll(header, desc);
+
+ headerBox.getChildren().addAll(
+ isAuthentication ? SvgIcons.certificatesIcon() : SvgIcons.cyberShieldLogo(26),
+ titleBox
+ );
// Caixa de Detalhes
VBox detailsBox = new VBox(12);
@@ -231,7 +242,7 @@ public class SignaturePromptDialog {
actionsRow.getChildren().addAll(btnCancel, btnConfirm);
- root.getChildren().addAll(header, desc, detailsBox, validationLabel, actionsRow);
+ root.getChildren().addAll(headerBox, detailsBox, validationLabel, actionsRow);
Scene scene = new Scene(root);
ThemeManager.applyTheme(scene);
diff --git a/modules/cybercore-ui/src/main/resources/assets/README.md b/modules/cybercore-ui/src/main/resources/assets/README.md
index d77f68b..91f8eef 100644
--- a/modules/cybercore-ui/src/main/resources/assets/README.md
+++ b/modules/cybercore-ui/src/main/resources/assets/README.md
@@ -1,6 +1,6 @@
-# Pasta de Ícones (SVG) e Imagens (PNG) — CyberCore Signer
+# Recursos Visuais (Ícones e Imagens em SVG) — CyberCore Signer
-Esta pasta é o repositório oficial onde você pode visualizar, customizar e substituir livremente os **ícones em SVG** e as **imagens em PNG** do **CyberCore Signer**.
+Esta pasta é o repositório oficial onde você pode visualizar, customizar e substituir livremente todos os **ícones em SVG** e as **imagens em SVG** do **CyberCore Signer**.
---
@@ -31,13 +31,23 @@ assets/
│ ├── seal.svg # Ícone Designer e Gerenciador de Selos Notariais
│ ├── check.svg # Ícone Checkmark de sucesso e validação
│ ├── error.svg # Ícone de Erro / Alerta em vermelho
-│ └── star.svg # Ícone de Estrela notarial / favorita
+│ ├── star.svg # Ícone de Estrela notarial / favorita
+│ ├── close.svg # Ícone Fechar / Cancelar (cruz geométrica moderna)
+│ ├── arrow-right.svg # Ícone Avançar / Seta direcional
+│ ├── zoom-in.svg # Ícone Zoom In (+)
+│ ├── zoom-out.svg # Ícone Zoom Out (-)
+│ ├── zoom-fit.svg # Ícone Ajustar à Tela
+│ └── refresh.svg # Ícone Atualizar / Recarregar listas e status
│
-└── images/ # Imagens de alta fidelidade em PNG com canal alfa (transparência)
- ├── app-icon.png # Ícone oficial do app (256x256 px - Diamante 3D azul Serpro)
- ├── app-logo.png # Logotipo horizontal oficial (480x120 px - CyberCore SIGNER)
- ├── brand-banner.png # Banner de cabeçalho e marca (600x160 px - Serpro Blue)
- └── default-seal.png # Selo de assinatura padrão ICP-Brasil (640x164 px)
+└── images/ # Imagens vetoriais SVG editáveis (com cópias PNG de fallback)
+ ├── app-icon.svg # Ícone oficial do app em SVG (Cristal/Diamante 3D azul)
+ ├── app-icon.png # Fallback em PNG (256x256 px)
+ ├── app-logo.svg # Logotipo horizontal oficial em SVG (CyberCore SIGNER)
+ ├── app-logo.png # Fallback em PNG (480x120 px)
+ ├── brand-banner.svg # Banner de cabeçalho e marca em SVG (600x160 px)
+ ├── brand-banner.png # Fallback em PNG (600x160 px)
+ ├── default-seal.svg # Selo de assinatura padrão ICP-Brasil em SVG (640x164 px)
+ └── default-seal.png # Fallback em PNG (640x164 px)
```
---
@@ -45,20 +55,21 @@ assets/
## 🎨 Como Customizar Qualquer Ícone ou Imagem
1. **Ícones:**
- - Estão no formato `.svg` padrão.
- - Podem ser editados diretamente no seu editor de código (VS Code, etc.) ou em editores vetoriais como **Inkscape, Figma ou Illustrator**.
- - Se preferir colocar uma imagem bitmap para um ícone específico, você também pode colocar um `.png` (ex: `pdf.png`). O sistema prioriza `.svg` e aceita `.png` com fallback transparente.
+ - Todos estão no formato `.svg` padrão da W3C.
+ - Podem ser abertos e editados diretamente em editores de texto/código (VS Code) ou ferramentas vetoriais como **Inkscape, Figma, Adobe Illustrator**.
+ - Se preferir usar um arquivo bitmap para algum ícone específico, basta salvar um `.png` correspondente na pasta (ex: `pdf.png`). O sistema detecta e renderiza automaticamente.
2. **Imagens do Sistema:**
- - Estão no formato `.png` com canal alfa de 32 bits (fundo transparente).
- - Você pode substituir `app-logo.png`, `app-icon.png`, `brand-banner.png` ou `default-seal.png` pelo logotipo e identidade visual da sua instituição ou empresa.
+ - Tanto o logotipo horizontal (`app-logo.svg`), o ícone do sistema (`app-icon.svg`), o banner institucional (`brand-banner.svg`) e o selo notarial padrão (`default-seal.svg`) agora são arquivos **SVG vetoriais editáveis**.
+ - Você pode alterar cores, tipografias, brasões e dimensões diretamente nos arquivos SVG.
+ - O `AssetManager` renderiza os arquivos SVG preservando fidelidade geométrica e suportando gradientes e alinhamentos.
-3. **Ordem de Prioridade e Hot-Reloading:**
- Quando a aplicação busca um ícone ou imagem, a resolução ocorre na ordem:
- 1. **Pasta Física Local (`./assets/icons/` e `./assets/images/`)**: Qualquer alteração feita aqui entra em vigor imediatamente ao abrir a janela ou reiniciar a aplicação, sem necessidade de recompilar!
- 2. **Pasta do Usuário (`~/.cybercore/assets/`)**: Configurações específicas por usuário do sistema operacional.
- 3. **Resources Empacotados no JAR (`/assets/`)**: Cópia empacotada internamente no `.jar`.
- 4. **Fallback Vetorial Nativo (`SvgIcons`)**: Garantia de que a interface nunca fique em branco caso algum arquivo seja excluído.
+3. **Ordem de Resolução e Carregamento:**
+ Ao requisitar qualquer elemento gráfico, a aplicação segue a ordem de prioridade:
+ 1. **Pasta Física Local (`./assets/icons/` e `./assets/images/`)**: Arquivos `.svg` editados aqui têm prioridade máxima e entram em vigor imediatamente sem necessidade de recompilar.
+ 2. **Pasta do Usuário (`~/.cybercore/assets/`)**: Para customizações no diretório pessoal do usuário.
+ 3. **Recursos Empacotados no JAR (`/assets/`)**: SVGs e imagens internas empacotadas no pacote de distribuição.
+ 4. **Fallback Vetorial Nativo (`SvgIcons`)**: Garantia de que a interface nunca fique desprovida de grafismos caso algum arquivo seja removido.
---
diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/about.svg b/modules/cybercore-ui/src/main/resources/assets/icons/about.svg
index dd4abb9..f6aac90 100644
--- a/modules/cybercore-ui/src/main/resources/assets/icons/about.svg
+++ b/modules/cybercore-ui/src/main/resources/assets/icons/about.svg
@@ -1,5 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/arrow-right.svg b/modules/cybercore-ui/src/main/resources/assets/icons/arrow-right.svg
new file mode 100644
index 0000000..1db6d0c
--- /dev/null
+++ b/modules/cybercore-ui/src/main/resources/assets/icons/arrow-right.svg
@@ -0,0 +1,3 @@
+
diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/close.svg b/modules/cybercore-ui/src/main/resources/assets/icons/close.svg
new file mode 100644
index 0000000..3f6cc79
--- /dev/null
+++ b/modules/cybercore-ui/src/main/resources/assets/icons/close.svg
@@ -0,0 +1,3 @@
+
diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/moon.svg b/modules/cybercore-ui/src/main/resources/assets/icons/moon.svg
index fd47105..9eb6f1a 100644
--- a/modules/cybercore-ui/src/main/resources/assets/icons/moon.svg
+++ b/modules/cybercore-ui/src/main/resources/assets/icons/moon.svg
@@ -1,6 +1,6 @@
diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/refresh.svg b/modules/cybercore-ui/src/main/resources/assets/icons/refresh.svg
new file mode 100644
index 0000000..8ee09d9
--- /dev/null
+++ b/modules/cybercore-ui/src/main/resources/assets/icons/refresh.svg
@@ -0,0 +1,5 @@
+
diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/settings.svg b/modules/cybercore-ui/src/main/resources/assets/icons/settings.svg
index e0f4042..0a8dbc2 100644
--- a/modules/cybercore-ui/src/main/resources/assets/icons/settings.svg
+++ b/modules/cybercore-ui/src/main/resources/assets/icons/settings.svg
@@ -1,8 +1,4 @@
diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/zoom-fit.svg b/modules/cybercore-ui/src/main/resources/assets/icons/zoom-fit.svg
new file mode 100644
index 0000000..201271c
--- /dev/null
+++ b/modules/cybercore-ui/src/main/resources/assets/icons/zoom-fit.svg
@@ -0,0 +1,3 @@
+
diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/zoom-in.svg b/modules/cybercore-ui/src/main/resources/assets/icons/zoom-in.svg
new file mode 100644
index 0000000..6783075
--- /dev/null
+++ b/modules/cybercore-ui/src/main/resources/assets/icons/zoom-in.svg
@@ -0,0 +1,5 @@
+
diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/zoom-out.svg b/modules/cybercore-ui/src/main/resources/assets/icons/zoom-out.svg
new file mode 100644
index 0000000..3882ea9
--- /dev/null
+++ b/modules/cybercore-ui/src/main/resources/assets/icons/zoom-out.svg
@@ -0,0 +1,5 @@
+
diff --git a/modules/cybercore-ui/src/main/resources/assets/images/app-icon.svg b/modules/cybercore-ui/src/main/resources/assets/images/app-icon.svg
new file mode 100644
index 0000000..10d2692
--- /dev/null
+++ b/modules/cybercore-ui/src/main/resources/assets/images/app-icon.svg
@@ -0,0 +1,37 @@
+
diff --git a/modules/cybercore-ui/src/main/resources/assets/images/app-logo.svg b/modules/cybercore-ui/src/main/resources/assets/images/app-logo.svg
new file mode 100644
index 0000000..ed887e6
--- /dev/null
+++ b/modules/cybercore-ui/src/main/resources/assets/images/app-logo.svg
@@ -0,0 +1,41 @@
+
diff --git a/modules/cybercore-ui/src/main/resources/assets/images/brand-banner.svg b/modules/cybercore-ui/src/main/resources/assets/images/brand-banner.svg
new file mode 100644
index 0000000..d133d1c
--- /dev/null
+++ b/modules/cybercore-ui/src/main/resources/assets/images/brand-banner.svg
@@ -0,0 +1,32 @@
+
diff --git a/modules/cybercore-ui/src/main/resources/assets/images/default-seal.svg b/modules/cybercore-ui/src/main/resources/assets/images/default-seal.svg
new file mode 100644
index 0000000..4b7c026
--- /dev/null
+++ b/modules/cybercore-ui/src/main/resources/assets/images/default-seal.svg
@@ -0,0 +1,34 @@
+
diff --git a/modules/cybercore-ui/src/test/java/br/com/cybercore/signer/ui/assets/AssetManagerTest.java b/modules/cybercore-ui/src/test/java/br/com/cybercore/signer/ui/assets/AssetManagerTest.java
new file mode 100644
index 0000000..0b26b0d
--- /dev/null
+++ b/modules/cybercore-ui/src/test/java/br/com/cybercore/signer/ui/assets/AssetManagerTest.java
@@ -0,0 +1,58 @@
+package br.com.cybercore.signer.ui.assets;
+
+import br.com.cybercore.signer.ui.components.SvgIcons;
+import javafx.scene.Node;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.io.InputStream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class AssetManagerTest {
+
+ @Test
+ @DisplayName("Deve carregar imagens institucionais em SVG do classpath ou pasta de assets")
+ void shouldLoadSvgImages() {
+ Node logo = AssetManager.getImageOrDefault("app-logo", 480, 120, null);
+ assertThat(logo).isNotNull();
+
+ Node banner = AssetManager.getImageOrDefault("brand-banner", 600, 160, null);
+ assertThat(banner).isNotNull();
+
+ Node icon = AssetManager.getImageOrDefault("app-icon", 256, 256, null);
+ assertThat(icon).isNotNull();
+
+ Node seal = AssetManager.getImageOrDefault("default-seal", 640, 164, null);
+ assertThat(seal).isNotNull();
+ }
+
+ @Test
+ @DisplayName("Deve carregar ícones SVG dinâmicos através do AssetManager")
+ void shouldLoadSvgIcons() {
+ String[] iconNames = {
+ "pdf", "xml", "file", "batch", "attached", "co-sign",
+ "timestamp", "ltv", "validate", "validate-timestamp", "exit",
+ "logs", "settings", "tutorial", "cert", "about", "sun", "moon",
+ "shield", "seal", "check", "error", "star", "close", "arrow-right",
+ "zoom-in", "zoom-out", "zoom-fit", "refresh"
+ };
+
+ for (String name : iconNames) {
+ Node iconNode = AssetManager.getIconOrDefault(name, 24, null);
+ assertThat(iconNode).withFailMessage("Ícone SVG não encontrado ou nulo: %s", name).isNotNull();
+ }
+ }
+
+ @Test
+ @DisplayName("Deve instanciar nós de ícones via fábrica SvgIcons com fallback")
+ void shouldCreateNodesViaSvgIcons() {
+ assertThat(SvgIcons.closeIcon(16)).isNotNull();
+ assertThat(SvgIcons.arrowRightIcon(16)).isNotNull();
+ assertThat(SvgIcons.zoomInIcon(16)).isNotNull();
+ assertThat(SvgIcons.zoomOutIcon(16)).isNotNull();
+ assertThat(SvgIcons.zoomFitIcon(16)).isNotNull();
+ assertThat(SvgIcons.refreshIcon(16)).isNotNull();
+ assertThat(SvgIcons.createAppCrystalLogo(48)).isNotNull();
+ }
+}