docs: add SVG design guide and update PDF and XML asset files

This commit is contained in:
Claudecio Martins
2026-10-12 13:40:07 -03:00
parent ef44d34944
commit 5551d9e667
8 changed files with 185 additions and 384 deletions
+1
View File
@@ -56,6 +56,7 @@ assets/
1. **Ícones:** 1. **Ícones:**
- Todos estão no formato `.svg` padrão da W3C. - Todos estão no formato `.svg` padrão da W3C.
- Consulte o guia completo de especificações em [docs/svg-design-guide.md](../docs/svg-design-guide.md) para tokens de cores, dimensões e boas práticas ao exportar do **Inkscape/Figma**.
- Podem ser abertos e editados diretamente em editores de texto/código (VS Code) ou ferramentas vetoriais como **Inkscape, Figma, Adobe Illustrator**. - 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. - 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.
+13 -96
View File
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 996 B

+8 -92
View File
@@ -1,97 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48">
<svg
viewBox="0 0 48 48"
width="48"
height="48"
version="1.1"
id="svg4"
sodipodi:docname="xml.svg"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs4" />
<sodipodi:namedview
id="namedview4"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="16.8125"
inkscape:cx="23.97026"
inkscape:cy="24"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!-- Folha de documento com dobra --> <!-- Folha de documento com dobra -->
<path <path d="M 8 4 L 32 4 L 42 14 L 42 44 L 8 44 Z" fill="#ffffff" stroke="#0288d1" stroke-width="2.5" stroke-linejoin="round"/>
d="M 8 4 L 32 4 L 42 14 L 42 44 L 8 44 Z"
fill="#ffffff"
stroke="#0288d1"
stroke-width="2.5"
stroke-linejoin="round"
id="path1" />
<!-- Dobra de canto --> <!-- Dobra de canto -->
<path <path d="M 32 4 L 32 14 L 42 14 Z" fill="#b3e5fc" stroke="#0288d1" stroke-width="2" stroke-linejoin="round"/>
d="M 32 4 L 32 14 L 42 14 Z"
fill="#b3e5fc"
stroke="#0288d1"
stroke-width="2"
stroke-linejoin="round"
id="path2" />
<!-- Linhas de texto representativas --> <!-- Linhas de texto representativas -->
<line <line x1="13" y1="18" x2="27" y2="18" stroke="#b0bec5" stroke-width="2" stroke-linecap="round"/>
x1="12.3" <line x1="13" y1="22" x2="35" y2="22" stroke="#b0bec5" stroke-width="2" stroke-linecap="round"/>
y1="17.20446" <line x1="13" y1="26" x2="35" y2="26" stroke="#b0bec5" stroke-width="2" stroke-linecap="round"/>
x2="26.299999"
y2="17.20446"
stroke="#b0bec5"
stroke-width="2"
stroke-linecap="round"
id="line2" />
<line
x1="12.3"
y1="20.747211"
x2="34.299999"
y2="20.747211"
stroke="#b0bec5"
stroke-width="2"
stroke-linecap="round"
id="line3" />
<line
x1="12.3"
y1="24.289963"
x2="34.299999"
y2="24.289963"
stroke="#b0bec5"
stroke-width="2"
stroke-linecap="round"
id="line4" />
<!-- Etiqueta azul com sigla XML --> <!-- Etiqueta azul com sigla XML -->
<rect <rect x="11" y="31" width="28" height="11" rx="3" fill="#0288d1"/>
x="11.3" <text x="25" y="39" font-family="'Segoe UI', Arial, sans-serif" font-size="8px" font-weight="bold" fill="#ffffff" text-anchor="middle">&lt;XML&gt;</text>
y="30.799257"
width="28"
height="11"
rx="3"
fill="#0288d1"
id="rect4" />
<text
x="25.296093"
y="39.100037"
font-family="'Segoe UI', Arial, sans-serif"
font-size="8px"
font-weight="bold"
fill="#ffffff"
text-anchor="middle"
id="text4">&lt;XML&gt;</text>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1000 B

+86
View File
@@ -0,0 +1,86 @@
# Guia de Padrão Visual e Design System SVG — CyberCore Signer
Este documento estabelece a especificação oficial de design, geometria, tokens de cores e regras de compatibilidade para todos os ícones e elementos gráficos vetoriais (**SVG**) do ecossistema **CyberCore Signer**.
---
## 1. Princípios de Design
1. **Clareza e Precisão:** Cada ícone deve comunicar imediatamente sua função técnica (PDF, XML, assinatura embutida, validação, credenciais).
2. **Minimalismo e Geometria Pura:** Evitar ruídos visuais, nós desnecessários, filtros proprietários ou imagens bitmap embutidas (`<image>`).
3. **Consistência de Traço e Escala:** Espessuras de contorno previsíveis e alinhadas ao grid de 48x48 pixels.
4. **Interoperabilidade:** Totalmente compatível com editores vetoriais modernos (**Inkscape, Figma, Adobe Illustrator**) e com o parser nativo de JavaFX em `AssetManager`.
---
## 2. Grid e Canvas Canônico
| Propriedade | Valor Padrão | Descrição |
|---|---|---|
| **ViewBox** | `0 0 48 48` | Padrão obrigatório para ícones da grade de ações e ferramentas laterais. |
| **Dimensões Base** | `width="48" height="48"` | Dimensões nominais 1:1. |
| **Área Segura** | `4px` de margem interna | O conteúdo vetorial deve se concentrar entre `(4, 4)` e `(44, 44)`. |
| **Raio de Canto Padrão** | `rx="3"` ou `rx="6"` | Arredondamentos harmônicos em cantos de janelas e etiquetas. |
---
## 3. Paleta de Cores e Tokens Visuais
Os ícones utilizam uma paleta de cores corporativa baseada em Material Design e Tailwind Modern, com alto contraste sobre fundos claros e escuros:
| Token | Hexadecimal | Papel / Significado |
|---|---|---|
| **`color-brand-primary`** | `#0288d1` | Azul CyberCore. Traço padrão de folhas, botões principais e XML. |
| **`color-brand-deep`** | `#01579b` | Azul escuro para dobras e acentos de profundidade. |
| **`color-paper-fold`** | `#b3e5fc` | Preenchimento suave da dobra superior do documento padrão. |
| **`color-pdf-accent`** | `#d32f2f` | Vermelho institucional do formato Adobe PDF. |
| **`color-pdf-fold`** | `#ffcdd2` | Preenchimento suave da dobra do documento PDF. |
| **`color-success`** | `#10b981` / `#2e7d32` | Verde esmeralda para assinaturas válidas e verificações. |
| **`color-gold-seal`** | `#f59e0b` / `#d97706` | Dourado e âmbar para selos notariais e certificação digital. |
| **`color-paper-lines`** | `#b0bec5` / `#cfd8dc` | Linhas de texto representativas e divisórias neutras. |
| **`color-console-bg`** | `#1e293b` | Fundo de janelas de terminal (Logs). |
| **`color-danger-exit`** | `#d32f2f` | Seta e alertas de encerramento do agente. |
---
## 4. Anatomia dos Ícones de Documentos
Para documentos com dobra (como PDF, XML, Atachada e Validador):
```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48">
<!-- 1. Folha de documento com dobra -->
<path d="M 8 4 L 32 4 L 42 14 L 42 44 L 8 44 Z"
fill="#ffffff" stroke="[COR_BORDA]" stroke-width="2.5" stroke-linejoin="round"/>
<!-- 2. Dobra de canto superior direito -->
<path d="M 32 4 L 32 14 L 42 14 Z"
fill="[COR_FUNDO_DOBRA]" stroke="[COR_BORDA]" stroke-width="2" stroke-linejoin="round"/>
<!-- 3. Linhas de texto representativas -->
<line x1="13" y1="18" x2="27" y2="18" stroke="#b0bec5" stroke-width="2" stroke-linecap="round"/>
<line x1="13" y1="22" x2="35" y2="22" stroke="#b0bec5" stroke-width="2" stroke-linecap="round"/>
<line x1="13" y1="26" x2="35" y2="26" stroke="#b0bec5" stroke-width="2" stroke-linecap="round"/>
<!-- 4. Elemento distintivo inferior (Etiqueta, Caneta, Clipe ou Checkmark) -->
<rect x="11" y="31" width="28" height="11" rx="3" fill="[COR_BADGE]"/>
<text x="25" y="39" font-family="'Segoe UI', Arial, sans-serif"
font-size="8px" font-weight="bold" fill="#ffffff" text-anchor="middle">[TEXTO]</text>
</svg>
```
---
## 5. Boas Práticas para Exportação no Inkscape / Figma
Ao criar ou editar ícones nas ferramentas vetoriais externas:
1. **Escolha a opção "SVG Simples" (Plain SVG) no Inkscape:**
- No Inkscape: `Arquivo → Salvar Como... → SVG Simples (*.svg)`.
- Isso elimina metadados de sessão (`sodipodi:namedview`, guias, zoom e histórico).
2. **Nunca incorpore imagens bitmap (`<image>` base64):**
- O aplicativo desktop deve conter exclusivamente geometria vetorial limpa (`path`, `rect`, `circle`, `line`, `polygon`, `polyline`, `text`).
3. **Suporte a `style="..."` inline:**
- O parser do CyberCore Signer (`AssetManager`) suporta tanto atributos XML puros (`fill="#..."`) quanto estilos inline em CSS (`style="fill:#...;stroke:#..."`).
4. **Mantenha o ViewBox Quadrado:**
- Preserve sempre `viewBox="0 0 48 48"` para garantir alinhamento perfeito nos botões e painéis da interface.
@@ -523,8 +523,11 @@ public final class AssetManager {
double fs = parseDouble(el.getAttribute("font-size"), 12); double fs = parseDouble(el.getAttribute("font-size"), 12);
String fw = el.getAttribute("font-weight"); String fw = el.getAttribute("font-weight");
t.setFont(Font.font("Segoe UI", "bold".equalsIgnoreCase(fw) ? FontWeight.BOLD : FontWeight.NORMAL, fs)); t.setFont(Font.font("Segoe UI", "bold".equalsIgnoreCase(fw) ? FontWeight.BOLD : FontWeight.NORMAL, fs));
if (el.hasAttribute("fill")) {
t.setFill(parsePaint(el.getAttribute("fill"), defsMap, Color.BLACK)); Map<String, String> inlineStyle = el.hasAttribute("style") ? parseStyleAttribute(el.getAttribute("style")) : Map.of();
String fillVal = inlineStyle.getOrDefault("fill", el.hasAttribute("fill") ? el.getAttribute("fill") : null);
if (fillVal != null) {
t.setFill(parsePaint(fillVal, defsMap, Color.BLACK));
} }
if ("middle".equalsIgnoreCase(el.getAttribute("text-anchor"))) { if ("middle".equalsIgnoreCase(el.getAttribute("text-anchor"))) {
t.setTextOrigin(javafx.geometry.VPos.CENTER); t.setTextOrigin(javafx.geometry.VPos.CENTER);
@@ -558,19 +561,46 @@ public final class AssetManager {
return null; return null;
} }
private static Map<String, String> parseStyleAttribute(String styleAttr) {
Map<String, String> styleMap = new HashMap<>();
if (styleAttr == null || styleAttr.isBlank()) {
return styleMap;
}
String[] declarations = styleAttr.split(";");
for (String decl : declarations) {
int colonIdx = decl.indexOf(':');
if (colonIdx > 0) {
String prop = decl.substring(0, colonIdx).trim().toLowerCase();
String val = decl.substring(colonIdx + 1).trim();
styleMap.put(prop, val);
}
}
return styleMap;
}
private static void applyShapeStyles(Element el, Shape shape, Map<String, Paint> defsMap) { private static void applyShapeStyles(Element el, Shape shape, Map<String, Paint> defsMap) {
if (el.hasAttribute("fill")) { Map<String, String> inlineStyle = el.hasAttribute("style") ? parseStyleAttribute(el.getAttribute("style")) : Map.of();
shape.setFill(parsePaint(el.getAttribute("fill"), defsMap, Color.TRANSPARENT));
String fillVal = inlineStyle.getOrDefault("fill", el.hasAttribute("fill") ? el.getAttribute("fill") : null);
if (fillVal != null) {
shape.setFill(parsePaint(fillVal, defsMap, Color.TRANSPARENT));
} else if (shape instanceof Line) { } else if (shape instanceof Line) {
shape.setFill(Color.TRANSPARENT); shape.setFill(Color.TRANSPARENT);
} }
if (el.hasAttribute("stroke")) { String strokeVal = inlineStyle.getOrDefault("stroke", el.hasAttribute("stroke") ? el.getAttribute("stroke") : null);
shape.setStroke(parsePaint(el.getAttribute("stroke"), defsMap, Color.TRANSPARENT)); if (strokeVal != null) {
shape.setStroke(parsePaint(strokeVal, defsMap, Color.TRANSPARENT));
} }
if (el.hasAttribute("stroke-width")) { String strokeWidthVal = inlineStyle.getOrDefault("stroke-width", el.hasAttribute("stroke-width") ? el.getAttribute("stroke-width") : null);
shape.setStrokeWidth(parseDouble(el.getAttribute("stroke-width"), 1.0)); if (strokeWidthVal != null) {
shape.setStrokeWidth(parseDouble(strokeWidthVal, 1.0));
}
String opacityVal = inlineStyle.getOrDefault("opacity", el.hasAttribute("opacity") ? el.getAttribute("opacity") : null);
if (opacityVal != null) {
shape.setOpacity(parseDouble(opacityVal, 1.0));
} }
if (el.hasAttribute("fill-rule") && shape instanceof SVGPath sp) { if (el.hasAttribute("fill-rule") && shape instanceof SVGPath sp) {
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 996 B

@@ -1,97 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48">
<svg
viewBox="0 0 48 48"
width="48"
height="48"
version="1.1"
id="svg4"
sodipodi:docname="xml.svg"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs4" />
<sodipodi:namedview
id="namedview4"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="16.8125"
inkscape:cx="23.97026"
inkscape:cy="24"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!-- Folha de documento com dobra --> <!-- Folha de documento com dobra -->
<path <path d="M 8 4 L 32 4 L 42 14 L 42 44 L 8 44 Z" fill="#ffffff" stroke="#0288d1" stroke-width="2.5" stroke-linejoin="round"/>
d="M 8 4 L 32 4 L 42 14 L 42 44 L 8 44 Z"
fill="#ffffff"
stroke="#0288d1"
stroke-width="2.5"
stroke-linejoin="round"
id="path1" />
<!-- Dobra de canto --> <!-- Dobra de canto -->
<path <path d="M 32 4 L 32 14 L 42 14 Z" fill="#b3e5fc" stroke="#0288d1" stroke-width="2" stroke-linejoin="round"/>
d="M 32 4 L 32 14 L 42 14 Z"
fill="#b3e5fc"
stroke="#0288d1"
stroke-width="2"
stroke-linejoin="round"
id="path2" />
<!-- Linhas de texto representativas --> <!-- Linhas de texto representativas -->
<line <line x1="13" y1="18" x2="27" y2="18" stroke="#b0bec5" stroke-width="2" stroke-linecap="round"/>
x1="12.3" <line x1="13" y1="22" x2="35" y2="22" stroke="#b0bec5" stroke-width="2" stroke-linecap="round"/>
y1="17.20446" <line x1="13" y1="26" x2="35" y2="26" stroke="#b0bec5" stroke-width="2" stroke-linecap="round"/>
x2="26.299999"
y2="17.20446"
stroke="#b0bec5"
stroke-width="2"
stroke-linecap="round"
id="line2" />
<line
x1="12.3"
y1="20.747211"
x2="34.299999"
y2="20.747211"
stroke="#b0bec5"
stroke-width="2"
stroke-linecap="round"
id="line3" />
<line
x1="12.3"
y1="24.289963"
x2="34.299999"
y2="24.289963"
stroke="#b0bec5"
stroke-width="2"
stroke-linecap="round"
id="line4" />
<!-- Etiqueta azul com sigla XML --> <!-- Etiqueta azul com sigla XML -->
<rect <rect x="11" y="31" width="28" height="11" rx="3" fill="#0288d1"/>
x="11.3" <text x="25" y="39" font-family="'Segoe UI', Arial, sans-serif" font-size="8px" font-weight="bold" fill="#ffffff" text-anchor="middle">&lt;XML&gt;</text>
y="30.799257"
width="28"
height="11"
rx="3"
fill="#0288d1"
id="rect4" />
<text
x="25.296093"
y="39.100037"
font-family="'Segoe UI', Arial, sans-serif"
font-size="8px"
font-weight="bold"
fill="#ffffff"
text-anchor="middle"
id="text4">&lt;XML&gt;</text>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1000 B

@@ -55,4 +55,22 @@ class AssetManagerTest {
assertThat(SvgIcons.refreshIcon(16)).isNotNull(); assertThat(SvgIcons.refreshIcon(16)).isNotNull();
assertThat(SvgIcons.createAppCrystalLogo(48)).isNotNull(); assertThat(SvgIcons.createAppCrystalLogo(48)).isNotNull();
} }
@Test
@DisplayName("Deve interpretar atributos style inline no padrão de exportação do Inkscape/Figma")
void shouldParseSvgWithInlineStyles() {
String svgWithStyle = """
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48">
<rect x="10" y="10" width="28" height="28" style="fill:#d32f2f;stroke:#0288d1;stroke-width:2.5;opacity:0.9"/>
<text x="24" y="24" style="fill:#ffffff;font-size:10px">OK</text>
</svg>
""";
try (var in = new java.io.ByteArrayInputStream(svgWithStyle.getBytes(java.nio.charset.StandardCharsets.UTF_8))) {
Node node = AssetManager.parseSvgToNode(in, 48, 48);
assertThat(node).isNotNull();
assertThat(node).isInstanceOf(javafx.scene.layout.Pane.class);
} catch (Exception e) {
org.junit.jupiter.api.Assertions.fail("Falha ao analisar SVG com estilo inline: " + e.getMessage());
}
}
} }