feat: implement in-memory log buffer, add ICP-Brasil seal assets, and improve SDK parameter handling
This commit is contained in:
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package br.com.cybercore.signer.app.log;
|
||||||
|
|
||||||
|
import br.com.cybercore.signer.core.log.AppLogBuffer;
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
|
import ch.qos.logback.core.AppenderBase;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appender do Logback que intercepta eventos reais de log e os encaminha para o buffer em memória.
|
||||||
|
*/
|
||||||
|
public class InMemoryLogAppender extends AppenderBase<ILoggingEvent> {
|
||||||
|
|
||||||
|
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void append(ILoggingEvent eventObject) {
|
||||||
|
if (eventObject == null) return;
|
||||||
|
|
||||||
|
LocalDateTime time = LocalDateTime.ofInstant(
|
||||||
|
Instant.ofEpochMilli(eventObject.getTimeStamp()),
|
||||||
|
ZoneId.systemDefault()
|
||||||
|
);
|
||||||
|
String timestamp = time.format(FORMATTER);
|
||||||
|
String level = eventObject.getLevel().toString();
|
||||||
|
String loggerName = eventObject.getLoggerName();
|
||||||
|
if (loggerName != null && loggerName.length() > 30) {
|
||||||
|
int lastDot = loggerName.lastIndexOf('.');
|
||||||
|
if (lastDot > 0) {
|
||||||
|
loggerName = loggerName.substring(lastDot + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String message = eventObject.getFormattedMessage();
|
||||||
|
|
||||||
|
String line = String.format("[%s] [%s] %s - %s", timestamp, level, loggerName, message);
|
||||||
|
AppLogBuffer.append(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,8 +6,11 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
|
<appender name="MEMORY" class="br.com.cybercore.signer.app.log.InMemoryLogAppender" />
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="CONSOLE" />
|
<appender-ref ref="CONSOLE" />
|
||||||
|
<appender-ref ref="MEMORY" />
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
<logger name="br.com.cybercore.signer" level="DEBUG" />
|
<logger name="br.com.cybercore.signer" level="DEBUG" />
|
||||||
|
|||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
package br.com.cybercore.signer.core.log;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buffer circular thread-safe em memória para armazenar eventos reais de log da aplicação,
|
||||||
|
* permitindo auditoria visual em tempo real pela interface gráfica desktop.
|
||||||
|
*/
|
||||||
|
public final class AppLogBuffer {
|
||||||
|
|
||||||
|
private static final int MAX_ENTRIES = 1000;
|
||||||
|
private static final Deque<String> BUFFER = new ArrayDeque<>(MAX_ENTRIES);
|
||||||
|
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
||||||
|
|
||||||
|
static {
|
||||||
|
appendSystemMessage("CyberCore Signer iniciado. Aguardando eventos criptográficos e conexões locais...");
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppLogBuffer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized void append(String logLine) {
|
||||||
|
if (logLine == null || logLine.isBlank()) return;
|
||||||
|
if (BUFFER.size() >= MAX_ENTRIES) {
|
||||||
|
BUFFER.pollFirst();
|
||||||
|
}
|
||||||
|
BUFFER.addLast(logLine.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized void appendSystemMessage(String msg) {
|
||||||
|
String timestamp = LocalDateTime.now().format(FORMATTER);
|
||||||
|
append("[" + timestamp + "] [INFO] " + msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized List<String> getLogs() {
|
||||||
|
return new ArrayList<>(BUFFER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized void clear() {
|
||||||
|
BUFFER.clear();
|
||||||
|
appendSystemMessage("Histórico de logs limpo pelo usuário.");
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
-26
@@ -112,8 +112,10 @@ public final class PdfSealRenderer {
|
|||||||
String headerText = resolveHeaderText(theme);
|
String headerText = resolveHeaderText(theme);
|
||||||
String footerText = resolveFooterText(theme, null);
|
String footerText = resolveFooterText(theme, null);
|
||||||
|
|
||||||
|
Color footerColor = new Color(100, 116, 139); // Cinza corporativo neutro e sóbrio (#64748b)
|
||||||
|
|
||||||
if (hasIdentity && hasDate) {
|
if (hasIdentity && hasDate) {
|
||||||
// Layout com 5 linhas: Cabeçalho, Nome, Identificação (CPF/Matrícula), Data, Rodapé
|
// Layout com 5 linhas: Cabeçalho, Nome, Identificação (CPF/CNPJ/Matrícula), Data, Rodapé
|
||||||
g2d.setColor(primaryColor);
|
g2d.setColor(primaryColor);
|
||||||
g2d.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 9));
|
g2d.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 9));
|
||||||
g2d.drawString(headerText, textX, 16);
|
g2d.drawString(headerText, textX, 16);
|
||||||
@@ -127,9 +129,8 @@ public final class PdfSealRenderer {
|
|||||||
|
|
||||||
g2d.drawString("Data: " + data.signingDate(), textX, 60);
|
g2d.drawString("Data: " + data.signingDate(), textX, 60);
|
||||||
|
|
||||||
g2d.setColor(secondaryColor);
|
g2d.setColor(footerColor);
|
||||||
g2d.setFont(new Font(Font.SANS_SERIF, Font.ITALIC, 8));
|
drawFittedText(g2d, footerText, textX, 73, textWidthAvailable, 7.8f);
|
||||||
g2d.drawString(footerText, textX, 73);
|
|
||||||
|
|
||||||
} else if (hasIdentity || hasDate) {
|
} else if (hasIdentity || hasDate) {
|
||||||
// Layout com 4 linhas: Cabeçalho, Nome, (Identificação ou Data), Rodapé
|
// Layout com 4 linhas: Cabeçalho, Nome, (Identificação ou Data), Rodapé
|
||||||
@@ -145,9 +146,8 @@ public final class PdfSealRenderer {
|
|||||||
String singleLineInfo = hasIdentity ? identityLine : ("Data: " + data.signingDate());
|
String singleLineInfo = hasIdentity ? identityLine : ("Data: " + data.signingDate());
|
||||||
drawFittedText(g2d, singleLineInfo, textX, 53, textWidthAvailable, 10.0f);
|
drawFittedText(g2d, singleLineInfo, textX, 53, textWidthAvailable, 10.0f);
|
||||||
|
|
||||||
g2d.setColor(secondaryColor);
|
g2d.setColor(footerColor);
|
||||||
g2d.setFont(new Font(Font.SANS_SERIF, Font.ITALIC, 8));
|
drawFittedText(g2d, footerText, textX, 71, textWidthAvailable, 7.8f);
|
||||||
g2d.drawString(footerText, textX, 71);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Layout com 3 linhas: Cabeçalho, Nome em Destaque Máximo, Rodapé
|
// Layout com 3 linhas: Cabeçalho, Nome em Destaque Máximo, Rodapé
|
||||||
@@ -158,9 +158,8 @@ public final class PdfSealRenderer {
|
|||||||
g2d.setColor(textColor);
|
g2d.setColor(textColor);
|
||||||
drawFullSignerName(g2d, fullName, textX, 44, textWidthAvailable, 14.5f);
|
drawFullSignerName(g2d, fullName, textX, 44, textWidthAvailable, 14.5f);
|
||||||
|
|
||||||
g2d.setColor(secondaryColor);
|
g2d.setColor(footerColor);
|
||||||
g2d.setFont(new Font(Font.SANS_SERIF, Font.ITALIC, 9));
|
drawFittedText(g2d, footerText, textX, 68, textWidthAvailable, 8.0f);
|
||||||
g2d.drawString(footerText, textX, 68);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
@@ -250,19 +249,28 @@ public final class PdfSealRenderer {
|
|||||||
* Formata os dados de documento (CPF/CNPJ) de acordo com o modo de exibição configurado.
|
* Formata os dados de documento (CPF/CNPJ) de acordo com o modo de exibição configurado.
|
||||||
*/
|
*/
|
||||||
private static String formatSignerDoc(PdfSealData data) {
|
private static String formatSignerDoc(PdfSealData data) {
|
||||||
CpfDisplayMode mode = data.cpfDisplayMode() != null ? data.cpfDisplayMode() : CpfDisplayMode.FULL;
|
String rawDoc = data.identifier() != null ? data.identifier().trim() : "";
|
||||||
String rawDoc = data.identifier() != null ? data.identifier() : "";
|
if (rawDoc.isBlank()) return "";
|
||||||
|
|
||||||
if (mode == CpfDisplayMode.NONE) {
|
String digits = rawDoc.replaceAll("\\D", "");
|
||||||
// Se for CPF, oculta. Se for CNPJ, pode manter ou formatar.
|
|
||||||
if (rawDoc.toUpperCase().contains("CPF")) {
|
// Pessoa Jurídica (14 dígitos): Rótulo CNPJ, SEMPRE completo, sem mascaramento
|
||||||
return "";
|
if (digits.length() == 14 || rawDoc.toUpperCase().contains("CNPJ")) {
|
||||||
|
if (digits.length() == 14) {
|
||||||
|
return "CNPJ: " + String.format("%s.%s.%s/%s-%s",
|
||||||
|
digits.substring(0, 2), digits.substring(2, 5), digits.substring(5, 8),
|
||||||
|
digits.substring(8, 12), digits.substring(12, 14));
|
||||||
}
|
}
|
||||||
return rawDoc;
|
return rawDoc.toUpperCase().startsWith("CNPJ") ? rawDoc : "CNPJ: " + rawDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rawDoc.toUpperCase().contains("CPF")) {
|
// Pessoa Física (11 dígitos): Rótulo CPF, respeitando CpfDisplayMode
|
||||||
String digits = rawDoc.replaceAll("\\D", "");
|
CpfDisplayMode mode = data.cpfDisplayMode() != null ? data.cpfDisplayMode() : CpfDisplayMode.FULL;
|
||||||
|
if (mode == CpfDisplayMode.NONE) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (digits.length() == 11) {
|
||||||
String formatted = mode.formatCpf(digits);
|
String formatted = mode.formatCpf(digits);
|
||||||
return formatted.isBlank() ? "" : "CPF: " + formatted;
|
return formatted.isBlank() ? "" : "CPF: " + formatted;
|
||||||
}
|
}
|
||||||
@@ -378,10 +386,10 @@ public final class PdfSealRenderer {
|
|||||||
return theme.customHeader().toUpperCase();
|
return theme.customHeader().toUpperCase();
|
||||||
}
|
}
|
||||||
return switch (theme.id().toLowerCase()) {
|
return switch (theme.id().toLowerCase()) {
|
||||||
case "cybercore-modern" -> "CYBERCORE SIGNER • AUTENTICAÇÃO DIGITAL";
|
case "cybercore-modern" -> "DOCUMENTO ASSINADO DIGITALMENTE";
|
||||||
case "minimal-clean" -> "DOCUMENTO ASSINADO DIGITALMENTE";
|
case "minimal-clean" -> "DOCUMENTO ASSINADO DIGITALMENTE";
|
||||||
case "gov-br" -> "GOV.BR • CONFORMIDADE MP Nº 2.200-2/2001";
|
case "gov-br" -> "DOCUMENTO ASSINADO DIGITALMENTE";
|
||||||
default -> "ASSINADO DIGITALMENTE • ICP-BRASIL";
|
default -> "DOCUMENTO ASSINADO DIGITALMENTE";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,10 +401,10 @@ public final class PdfSealRenderer {
|
|||||||
return "Hash Criptográfico: " + hash;
|
return "Hash Criptográfico: " + hash;
|
||||||
}
|
}
|
||||||
return switch (theme.id().toLowerCase()) {
|
return switch (theme.id().toLowerCase()) {
|
||||||
case "cybercore-modern" -> "Autenticidade assegurada por chave criptográfica local";
|
case "cybercore-modern" -> "Verifique a autenticidade em https://validar.iti.gov.br";
|
||||||
case "minimal-clean" -> "Assinatura qualificada padrão ICP-Brasil";
|
case "minimal-clean" -> "Verifique a autenticidade em https://validar.iti.gov.br";
|
||||||
case "gov-br" -> "Validade jurídica plena conforme MP nº 2.200-2/2001";
|
case "gov-br" -> "Verifique a autenticidade em https://validar.iti.gov.br";
|
||||||
default -> "Conformidade ICP-Brasil e validade jurídica plena";
|
default -> "Verifique a autenticidade em https://validar.iti.gov.br";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-107
@@ -35,10 +35,10 @@ public final class PdfSealThemeRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void registerBuiltInThemes() {
|
private static void registerBuiltInThemes() {
|
||||||
// 1. Tema ICP-Brasil Institucional
|
// Único Selo Padrão Embutido do Sistema: ICP-Brasil Institucional Oficial
|
||||||
registerTheme(new PdfSealTheme(
|
registerTheme(new PdfSealTheme(
|
||||||
"icp-brasil",
|
DEFAULT_THEME_ID,
|
||||||
"ICP-Brasil Institucional",
|
"ICP-Brasil Institucional (Padrão)",
|
||||||
"Estilo oficial corporativo com escudo de segurança vetorial e dados completos do titular",
|
"Estilo oficial corporativo com escudo de segurança vetorial e dados completos do titular",
|
||||||
320,
|
320,
|
||||||
82,
|
82,
|
||||||
@@ -51,7 +51,7 @@ public final class PdfSealThemeRegistry {
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 82" width="320" height="82">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 82" width="320" height="82">
|
||||||
<rect x="1" y="1" width="318" height="80" rx="6" fill="${THEME_BG}" stroke="${THEME_BORDER}" stroke-width="1.5"/>
|
<rect x="1" y="1" width="318" height="80" rx="6" fill="${THEME_BG}" stroke="${THEME_BORDER}" stroke-width="1.5"/>
|
||||||
<rect x="1" y="1" width="6" height="80" rx="3" fill="${THEME_PRIMARY}"/>
|
<rect x="1" y="1" width="6" height="80" rx="3" fill="${THEME_PRIMARY}"/>
|
||||||
<!-- Escudo Criptográfico Vetorial -->
|
<!-- Escudo Criptográfico Vetorial ICP-Brasil -->
|
||||||
<g transform="translate(14, 18)">
|
<g transform="translate(14, 18)">
|
||||||
<path d="M18 2 L32 7 V18 C32 28 25 36 18 39 C11 36 4 28 4 18 V7 Z" fill="${THEME_PRIMARY}" opacity="0.12"/>
|
<path d="M18 2 L32 7 V18 C32 28 25 36 18 39 C11 36 4 28 4 18 V7 Z" fill="${THEME_PRIMARY}" opacity="0.12"/>
|
||||||
<path d="M18 2 L32 7 V18 C32 28 25 36 18 39 C11 36 4 28 4 18 V7 Z" fill="none" stroke="${THEME_PRIMARY}" stroke-width="2"/>
|
<path d="M18 2 L32 7 V18 C32 28 25 36 18 39 C11 36 4 28 4 18 V7 Z" fill="none" stroke="${THEME_PRIMARY}" stroke-width="2"/>
|
||||||
@@ -65,104 +65,7 @@ public final class PdfSealThemeRegistry {
|
|||||||
<text x="56" y="32" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="12" font-weight="bold" fill="${THEME_TEXT}">${SIGNER_NAME}</text>
|
<text x="56" y="32" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="12" font-weight="bold" fill="${THEME_TEXT}">${SIGNER_NAME}</text>
|
||||||
<text x="56" y="46" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8.5" fill="#475569">Doc: ${SIGNER_DOC} • AC: ${CERT_ISSUER}</text>
|
<text x="56" y="46" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8.5" fill="#475569">Doc: ${SIGNER_DOC} • AC: ${CERT_ISSUER}</text>
|
||||||
<text x="56" y="59" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8.5" fill="#475569">Data/Hora: ${SIGNING_DATE}</text>
|
<text x="56" y="59" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8.5" fill="#475569">Data/Hora: ${SIGNING_DATE}</text>
|
||||||
<text x="56" y="72" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="7.5" fill="${THEME_SECONDARY}">Verificação criptográfica válida e com fé pública</text>
|
<text x="56" y="72" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="7.5" fill="#64748b">Verifique a autenticidade em https://validar.iti.gov.br</text>
|
||||||
</svg>
|
|
||||||
"""
|
|
||||||
));
|
|
||||||
|
|
||||||
// 2. Tema CyberCore Moderno
|
|
||||||
registerTheme(new PdfSealTheme(
|
|
||||||
"cybercore-modern",
|
|
||||||
"CyberCore Moderno",
|
|
||||||
"Identidade tecnológica com cristal vetorial CyberCore e visual ciano/esmeralda",
|
|
||||||
320,
|
|
||||||
82,
|
|
||||||
"#0284c7",
|
|
||||||
"#10b981",
|
|
||||||
"#ffffff",
|
|
||||||
"#38bdf8",
|
|
||||||
"#0f172a",
|
|
||||||
"""
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 82" width="320" height="82">
|
|
||||||
<rect x="1" y="1" width="318" height="80" rx="8" fill="${THEME_BG}" stroke="${THEME_BORDER}" stroke-width="1.5"/>
|
|
||||||
<rect x="1" y="1" width="5" height="80" rx="2.5" fill="${THEME_SECONDARY}"/>
|
|
||||||
<!-- Cristal CyberCore Vetorial -->
|
|
||||||
<g transform="translate(14, 20)">
|
|
||||||
<polygon points="18,2 32,10 32,28 18,36 4,28 4,10" fill="${THEME_PRIMARY}" opacity="0.15"/>
|
|
||||||
<polygon points="18,2 32,10 32,28 18,36 4,28 4,10" fill="none" stroke="${THEME_PRIMARY}" stroke-width="1.8"/>
|
|
||||||
<line x1="18" y1="2" x2="18" y2="36" stroke="${THEME_SECONDARY}" stroke-width="1.2"/>
|
|
||||||
<line x1="4" y1="19" x2="32" y2="19" stroke="${THEME_PRIMARY}" stroke-width="1.2"/>
|
|
||||||
</g>
|
|
||||||
<!-- Dados da Assinatura -->
|
|
||||||
<text x="56" y="17" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8.5" font-weight="bold" fill="${THEME_PRIMARY}">CYBERCORE SIGNER • ASSINATURA ELETRÔNICA</text>
|
|
||||||
<text x="56" y="32" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="12" font-weight="bold" fill="${THEME_TEXT}">${SIGNER_NAME}</text>
|
|
||||||
<text x="56" y="46" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8.5" fill="#475569">${SIGNER_DOC} • ${CERT_ISSUER}</text>
|
|
||||||
<text x="56" y="59" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8.5" fill="#475569">Autenticado em: ${SIGNING_DATE}</text>
|
|
||||||
<text x="56" y="72" font-family="monospace" font-size="7.5" fill="${THEME_SECONDARY}">Hash: ${HASH_SUMMARY}</text>
|
|
||||||
</svg>
|
|
||||||
"""
|
|
||||||
));
|
|
||||||
|
|
||||||
// 3. Tema Minimalista Clássico
|
|
||||||
registerTheme(new PdfSealTheme(
|
|
||||||
"minimal-clean",
|
|
||||||
"Minimalista Clássico",
|
|
||||||
"Design monocromático sóbrio em tons de grafite, ideal para contratos e certidões",
|
|
||||||
300,
|
|
||||||
76,
|
|
||||||
"#334155",
|
|
||||||
"#64748b",
|
|
||||||
"#fafafa",
|
|
||||||
"#cbd5e1",
|
|
||||||
"#1e293b",
|
|
||||||
"""
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 76" width="300" height="76">
|
|
||||||
<rect x="1" y="1" width="298" height="74" rx="4" fill="${THEME_BG}" stroke="${THEME_BORDER}" stroke-width="1"/>
|
|
||||||
<!-- Ícone de Carimbo Notarial -->
|
|
||||||
<g transform="translate(12, 18)">
|
|
||||||
<circle cx="18" cy="18" r="16" fill="none" stroke="${THEME_PRIMARY}" stroke-width="1.5"/>
|
|
||||||
<circle cx="18" cy="18" r="13" fill="none" stroke="${THEME_PRIMARY}" stroke-width="0.8" stroke-dasharray="2,2"/>
|
|
||||||
<path d="M12 18 L16 22 L24 14" fill="none" stroke="${THEME_PRIMARY}" stroke-width="2"/>
|
|
||||||
</g>
|
|
||||||
<!-- Dados da Assinatura -->
|
|
||||||
<text x="52" y="17" font-family="Georgia, serif" font-size="9" font-weight="bold" fill="${THEME_PRIMARY}">Assinado Digitalmente</text>
|
|
||||||
<text x="52" y="32" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="11.5" font-weight="bold" fill="${THEME_TEXT}">${SIGNER_NAME}</text>
|
|
||||||
<text x="52" y="46" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="8" fill="#475569">${SIGNER_DOC} • ${CERT_ISSUER}</text>
|
|
||||||
<text x="52" y="58" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="8" fill="#475569">${SIGNING_DATE}</text>
|
|
||||||
<text x="52" y="69" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="7.5" fill="#64748b">Conforme MP 2.200-2/2001 e ICP-Brasil</text>
|
|
||||||
</svg>
|
|
||||||
"""
|
|
||||||
));
|
|
||||||
|
|
||||||
// 4. Tema Governamental (Gov.br / ITI)
|
|
||||||
registerTheme(new PdfSealTheme(
|
|
||||||
"gov-br",
|
|
||||||
"Governamental (Padrão Gov.br / ITI)",
|
|
||||||
"Faixa verde e ouro com declaração expressa de validade pela MP nº 2.200-2/2001",
|
|
||||||
320,
|
|
||||||
82,
|
|
||||||
"#047857",
|
|
||||||
"#eab308",
|
|
||||||
"#ffffff",
|
|
||||||
"#a7f3d0",
|
|
||||||
"#064e3b",
|
|
||||||
"""
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 82" width="320" height="82">
|
|
||||||
<rect x="1" y="1" width="318" height="80" rx="4" fill="${THEME_BG}" stroke="${THEME_BORDER}" stroke-width="1.5"/>
|
|
||||||
<rect x="1" y="1" width="6" height="80" rx="2" fill="${THEME_PRIMARY}"/>
|
|
||||||
<rect x="7" y="1" width="3" height="80" fill="${THEME_SECONDARY}"/>
|
|
||||||
<!-- Brasão Estilizado -->
|
|
||||||
<g transform="translate(16, 20)">
|
|
||||||
<circle cx="17" cy="18" r="15" fill="${THEME_PRIMARY}" opacity="0.1"/>
|
|
||||||
<circle cx="17" cy="18" r="15" fill="none" stroke="${THEME_PRIMARY}" stroke-width="1.5"/>
|
|
||||||
<path d="M17 6 L20 14 L28 14 L22 19 L24 27 L17 22 L10 27 L12 19 L6 14 L14 14 Z" fill="${THEME_SECONDARY}"/>
|
|
||||||
</g>
|
|
||||||
<!-- Dados da Assinatura -->
|
|
||||||
<text x="56" y="17" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8.5" font-weight="bold" fill="${THEME_PRIMARY}">GOV.BR • DOCUMENTO ASSINADO DIGITALMENTE</text>
|
|
||||||
<text x="56" y="32" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="12" font-weight="bold" fill="${THEME_TEXT}">${SIGNER_NAME}</text>
|
|
||||||
<text x="56" y="46" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8.5" fill="#065f46">Doc: ${SIGNER_DOC} • AC: ${CERT_ISSUER}</text>
|
|
||||||
<text x="56" y="59" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8.5" fill="#475569">Data/Hora: ${SIGNING_DATE}</text>
|
|
||||||
<text x="56" y="72" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="7.5" fill="#047857">Verifique a autenticidade da assinatura eletrônica em https://validar.iti.gov.br</text>
|
|
||||||
</svg>
|
</svg>
|
||||||
"""
|
"""
|
||||||
));
|
));
|
||||||
@@ -205,10 +108,9 @@ public final class PdfSealThemeRegistry {
|
|||||||
if (themeId == null) return false;
|
if (themeId == null) return false;
|
||||||
String idLower = themeId.toLowerCase();
|
String idLower = themeId.toLowerCase();
|
||||||
|
|
||||||
// Protege temas padrão
|
// Protege o tema padrão
|
||||||
if (idLower.equals("icp-brasil") || idLower.equals("cybercore-modern") ||
|
if (idLower.equals(DEFAULT_THEME_ID)) {
|
||||||
idLower.equals("minimal-clean") || idLower.equals("gov-br")) {
|
throw new IllegalArgumentException("Não é permitido excluir o selo padrão pré-instalado do sistema.");
|
||||||
throw new IllegalArgumentException("Não é permitido excluir os temas de selo pré-instalados do sistema.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean deleted = STORAGE.deleteSeal(idLower);
|
boolean deleted = STORAGE.deleteSeal(idLower);
|
||||||
@@ -218,7 +120,11 @@ public final class PdfSealThemeRegistry {
|
|||||||
|
|
||||||
public static Optional<PdfSealTheme> findById(String id) {
|
public static Optional<PdfSealTheme> findById(String id) {
|
||||||
if (id == null) return Optional.empty();
|
if (id == null) return Optional.empty();
|
||||||
return Optional.ofNullable(THEMES.get(id.toLowerCase()));
|
PdfSealTheme theme = THEMES.get(id.toLowerCase());
|
||||||
|
if (theme == null && ("cybercore-modern".equalsIgnoreCase(id) || "minimal-clean".equalsIgnoreCase(id) || "gov-br".equalsIgnoreCase(id))) {
|
||||||
|
return Optional.of(getDefaultTheme());
|
||||||
|
}
|
||||||
|
return Optional.ofNullable(theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<PdfSealTheme> getTheme(String id) {
|
public static Optional<PdfSealTheme> getTheme(String id) {
|
||||||
|
|||||||
+58
-18
@@ -595,6 +595,9 @@ public class CcspServer {
|
|||||||
sealTheme = PdfSealThemeRegistry.getDefaultTheme();
|
sealTheme = PdfSealThemeRegistry.getDefaultTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info("[CCSP REQ] Selo visual configurado: '{}' (custom={}, hasLogo={}, header='{}')",
|
||||||
|
sealTheme.name(), sealTheme.isCustom(), sealTheme.customLogoBase64() != null, sealTheme.customHeader());
|
||||||
|
|
||||||
PdfSealPosition position = PdfSealPosition.BOTTOM_RIGHT;
|
PdfSealPosition position = PdfSealPosition.BOTTOM_RIGHT;
|
||||||
if (request.x() != null && request.y() != null) {
|
if (request.x() != null && request.y() != null) {
|
||||||
position = PdfSealPosition.CUSTOM;
|
position = PdfSealPosition.CUSTOM;
|
||||||
@@ -916,34 +919,71 @@ public class CcspServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getMapString(Map<String, Object> map, String defaultVal, String... keys) {
|
||||||
|
if (map == null) return defaultVal;
|
||||||
|
for (String k : keys) {
|
||||||
|
Object v = map.get(k);
|
||||||
|
if (v != null && !v.toString().isBlank()) {
|
||||||
|
return v.toString().trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getMapInt(Map<String, Object> map, int defaultVal, String... keys) {
|
||||||
|
if (map == null) return defaultVal;
|
||||||
|
for (String k : keys) {
|
||||||
|
Object v = map.get(k);
|
||||||
|
if (v instanceof Number n) {
|
||||||
|
return n.intValue();
|
||||||
|
} else if (v != null) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(v.toString().trim());
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
|
||||||
private PdfSealTheme parseCustomSealTheme(Map<String, Object> map) {
|
private PdfSealTheme parseCustomSealTheme(Map<String, Object> map) {
|
||||||
if (map == null || map.isEmpty()) {
|
if (map == null || map.isEmpty()) {
|
||||||
return PdfSealThemeRegistry.getDefaultTheme();
|
return PdfSealThemeRegistry.getDefaultTheme();
|
||||||
}
|
}
|
||||||
String id = "custom-" + UUID.randomUUID().toString().substring(0, 8);
|
String id = "custom-" + UUID.randomUUID().toString().substring(0, 8);
|
||||||
String name = (String) map.getOrDefault("name", "Selo Personalizado");
|
String name = getMapString(map, "Selo Personalizado", "name", "theme_name", "title");
|
||||||
String description = (String) map.getOrDefault("description", "Selo customizado via aplicação web");
|
String description = getMapString(map, "Selo customizado via aplicação web", "description", "desc");
|
||||||
int width = map.containsKey("width") ? ((Number) map.get("width")).intValue() : 320;
|
int width = getMapInt(map, 320, "width", "w");
|
||||||
int height = map.containsKey("height") ? ((Number) map.get("height")).intValue() : 82;
|
int height = getMapInt(map, 82, "height", "h");
|
||||||
String primaryColor = (String) map.getOrDefault("primaryColor", "#0d47a1");
|
String primaryColor = getMapString(map, "#0d47a1", "primaryColor", "primary_color");
|
||||||
String secondaryColor = (String) map.getOrDefault("secondaryColor", "#0284c7");
|
String secondaryColor = getMapString(map, "#0284c7", "secondaryColor", "secondary_color");
|
||||||
String backgroundColor = (String) map.getOrDefault("backgroundColor", "#ffffff");
|
String backgroundColor = getMapString(map, "#ffffff", "backgroundColor", "background_color");
|
||||||
String borderColor = (String) map.getOrDefault("borderColor", "#cbd5e1");
|
String borderColor = getMapString(map, "#cbd5e1", "borderColor", "border_color");
|
||||||
String textColor = (String) map.getOrDefault("textColor", "#0f172a");
|
String textColor = getMapString(map, "#0f172a", "textColor", "text_color");
|
||||||
String customHeader = (String) map.get("customHeader");
|
String customHeader = getMapString(map, null, "customHeader", "custom_header", "title");
|
||||||
String customFooter = (String) map.get("customFooter");
|
String customFooter = getMapString(map, null, "customFooter", "custom_footer");
|
||||||
String iconStyle = (String) map.getOrDefault("iconStyle", "shield");
|
String iconStyle = getMapString(map, "shield", "iconStyle", "icon_style");
|
||||||
String customLogoBase64 = (String) map.get("customLogoBase64");
|
String customLogoBase64 = getMapString(map, null, "customLogoBase64", "custom_logo_base64", "logo", "logoBase64");
|
||||||
String customFieldLabel = (String) map.get("customFieldLabel");
|
String customFieldLabel = getMapString(map, null, "customFieldLabel", "custom_field_label");
|
||||||
String customFieldValue = (String) map.get("customFieldValue");
|
String customFieldValue = getMapString(map, null, "customFieldValue", "custom_field_value");
|
||||||
|
|
||||||
CpfDisplayMode cpfMode = CpfDisplayMode.FULL;
|
CpfDisplayMode cpfMode = CpfDisplayMode.FULL;
|
||||||
if (map.containsKey("defaultCpfMode") && map.get("defaultCpfMode") != null) {
|
String rawCpfMode = getMapString(map, null, "defaultCpfMode", "default_cpf_mode", "cpfMode", "cpf_mode");
|
||||||
|
if (rawCpfMode != null) {
|
||||||
try {
|
try {
|
||||||
cpfMode = CpfDisplayMode.valueOf(map.get("defaultCpfMode").toString().toUpperCase());
|
cpfMode = CpfDisplayMode.valueOf(rawCpfMode.toUpperCase());
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
boolean includeDateTime = !map.containsKey("defaultIncludeDateTime") || Boolean.TRUE.equals(map.get("defaultIncludeDateTime"));
|
|
||||||
|
boolean includeDateTime = true;
|
||||||
|
Object dtObj = map.containsKey("defaultIncludeDateTime") ? map.get("defaultIncludeDateTime")
|
||||||
|
: (map.containsKey("default_include_date_time") ? map.get("default_include_date_time")
|
||||||
|
: (map.containsKey("includeDateTime") ? map.get("includeDateTime")
|
||||||
|
: map.get("include_date_time")));
|
||||||
|
if (dtObj instanceof Boolean b) {
|
||||||
|
includeDateTime = b;
|
||||||
|
} else if (dtObj != null) {
|
||||||
|
includeDateTime = Boolean.parseBoolean(dtObj.toString());
|
||||||
|
}
|
||||||
|
|
||||||
return new PdfSealTheme(
|
return new PdfSealTheme(
|
||||||
id, name, description, width, height,
|
id, name, description, width, height,
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 54" width="36" height="54">
|
||||||
|
<!-- Escudo Criptográfico Vetorial ICP-Brasil -->
|
||||||
|
<path d="M18 4 L32 9 V24 C32 37 25 46 18 50 C11 46 4 37 4 24 V9 Z" fill="#0d47a1" opacity="0.14"/>
|
||||||
|
<path d="M18 4 L32 9 V24 C32 37 25 46 18 50 C11 46 4 37 4 24 V9 Z" fill="none" stroke="#0d47a1" stroke-width="2.2" stroke-linejoin="round"/>
|
||||||
|
|
||||||
|
<!-- Corpo do Cadeado de Segurança -->
|
||||||
|
<rect x="11" y="24" width="14" height="12" rx="2.5" fill="#0284c7"/>
|
||||||
|
|
||||||
|
<!-- Arco Superior do Cadeado -->
|
||||||
|
<path d="M13.5 24 V19 C13.5 16.5 15.5 14.5 18 14.5 C20.5 14.5 22.5 16.5 22.5 19 V24" fill="none" stroke="#0284c7" stroke-width="2.2" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- Orifício da Chave -->
|
||||||
|
<circle cx="18" cy="29" r="1.6" fill="#ffffff"/>
|
||||||
|
<path d="M17.3 29.5 L18.7 29.5 L19.2 33 L16.8 33 Z" fill="#ffffff"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 858 B |
+4
-5
@@ -38,17 +38,16 @@ public final class CertificateCardNode extends VBox {
|
|||||||
HBox topRow = new HBox(10);
|
HBox topRow = new HBox(10);
|
||||||
topRow.setAlignment(Pos.CENTER_LEFT);
|
topRow.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
|
||||||
Label titleLabel = new Label(certificate.commonName() != null ? certificate.commonName() : certificate.alias());
|
String rawName = certificate.commonName() != null ? certificate.commonName() : certificate.alias();
|
||||||
|
String cleanTitle = rawName != null && rawName.contains(":") ? rawName.split(":")[0].trim() : (rawName != null ? rawName.trim() : "Certificado Digital");
|
||||||
|
Label titleLabel = new Label(cleanTitle);
|
||||||
titleLabel.getStyleClass().add("cert-title");
|
titleLabel.getStyleClass().add("cert-title");
|
||||||
HBox.setHgrow(titleLabel, Priority.ALWAYS);
|
HBox.setHgrow(titleLabel, Priority.ALWAYS);
|
||||||
|
|
||||||
Label badgeLabel = new Label();
|
Label badgeLabel = new Label("[" + certificate.type().getCode() + "]");
|
||||||
if (certificate.type() == CertificateType.A1) {
|
if (certificate.type() == CertificateType.A1) {
|
||||||
badgeLabel.setText("A1 • Certificado local");
|
|
||||||
badgeLabel.getStyleClass().add("cert-badge-a1");
|
badgeLabel.getStyleClass().add("cert-badge-a1");
|
||||||
} else {
|
} else {
|
||||||
String tokenDesc = certificate.hardwareDeviceName() != null ? certificate.hardwareDeviceName() : "Token USB";
|
|
||||||
badgeLabel.setText("A3 • " + tokenDesc);
|
|
||||||
badgeLabel.getStyleClass().add("cert-badge-a3");
|
badgeLabel.getStyleClass().add("cert-badge-a3");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+35
-16
@@ -57,24 +57,43 @@ public final class LogsViewerDialog {
|
|||||||
txtLogs.getStyleClass().add("log-console-area");
|
txtLogs.getStyleClass().add("log-console-area");
|
||||||
VBox.setVgrow(txtLogs, Priority.ALWAYS);
|
VBox.setVgrow(txtLogs, Priority.ALWAYS);
|
||||||
|
|
||||||
loadInitialLogs();
|
loadLogs();
|
||||||
|
|
||||||
HBox footer = new HBox(10);
|
HBox footer = new HBox(10);
|
||||||
footer.setAlignment(Pos.CENTER_RIGHT);
|
footer.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
|
||||||
|
Button btnCopy = new Button("Copiar");
|
||||||
|
btnCopy.getStyleClass().addAll("btn-secondary", "btn-dialog");
|
||||||
|
btnCopy.setPrefWidth(90);
|
||||||
|
btnCopy.setOnAction(e -> {
|
||||||
|
javafx.scene.input.ClipboardContent content = new javafx.scene.input.ClipboardContent();
|
||||||
|
content.putString(txtLogs.getText());
|
||||||
|
javafx.scene.input.Clipboard.getSystemClipboard().setContent(content);
|
||||||
|
btnCopy.setText("✓ Copiado");
|
||||||
|
javafx.animation.PauseTransition pause = new javafx.animation.PauseTransition(javafx.util.Duration.seconds(2));
|
||||||
|
pause.setOnFinished(ev -> btnCopy.setText("Copiar"));
|
||||||
|
pause.play();
|
||||||
|
});
|
||||||
|
|
||||||
|
Button btnClear = new Button("Limpar");
|
||||||
|
btnClear.getStyleClass().addAll("btn-secondary", "btn-dialog");
|
||||||
|
btnClear.setPrefWidth(90);
|
||||||
|
btnClear.setOnAction(e -> {
|
||||||
|
br.com.cybercore.signer.core.log.AppLogBuffer.clear();
|
||||||
|
loadLogs();
|
||||||
|
});
|
||||||
|
|
||||||
Button btnRefresh = new Button("Atualizar");
|
Button btnRefresh = new Button("Atualizar");
|
||||||
btnRefresh.getStyleClass().addAll("btn-secondary", "btn-dialog");
|
btnRefresh.getStyleClass().addAll("btn-secondary", "btn-dialog");
|
||||||
btnRefresh.setPrefWidth(110);
|
btnRefresh.setPrefWidth(100);
|
||||||
btnRefresh.setMinWidth(110);
|
btnRefresh.setOnAction(e -> loadLogs());
|
||||||
btnRefresh.setOnAction(e -> loadInitialLogs());
|
|
||||||
|
|
||||||
Button btnClose = new Button(" Fechar", SvgIcons.closeIcon(13));
|
Button btnClose = new Button(" Fechar", SvgIcons.closeIcon(13));
|
||||||
btnClose.getStyleClass().addAll("btn-secondary", "btn-dialog");
|
btnClose.getStyleClass().addAll("btn-secondary", "btn-dialog");
|
||||||
btnClose.setPrefWidth(110);
|
btnClose.setPrefWidth(100);
|
||||||
btnClose.setMinWidth(110);
|
|
||||||
btnClose.setOnAction(e -> stage.close());
|
btnClose.setOnAction(e -> stage.close());
|
||||||
|
|
||||||
footer.getChildren().addAll(btnRefresh, btnClose);
|
footer.getChildren().addAll(btnCopy, btnClear, btnRefresh, btnClose);
|
||||||
|
|
||||||
root.getChildren().addAll(header, txtLogs, footer);
|
root.getChildren().addAll(header, txtLogs, footer);
|
||||||
|
|
||||||
@@ -84,15 +103,15 @@ public final class LogsViewerDialog {
|
|||||||
stage.setScene(scene);
|
stage.setScene(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadInitialLogs() {
|
private void loadLogs() {
|
||||||
String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
java.util.List<String> logs = br.com.cybercore.signer.core.log.AppLogBuffer.getLogs();
|
||||||
String sb = "[" + now + "] [INFO] CyberCore Signer inicializado com sucesso.\n" +
|
if (logs.isEmpty()) {
|
||||||
"[" + now + "] [INFO] Servidor local CCSP/1 escutando em http://127.0.0.1:21325\n" +
|
txtLogs.setText("[Aguardando eventos de log...]");
|
||||||
"[" + now + "] [INFO] Provedores criptográficos registrados: Windows Certificate Store, PKCS#12, PKCS#11.\n" +
|
} else {
|
||||||
"[" + now + "] [INFO] SmartCardWatcher ativo (monitoramento de tokens USB e Smart Cards ICP-Brasil).\n" +
|
txtLogs.setText(String.join("\n", logs) + "\n");
|
||||||
"[" + now + "] [INFO] Política anti-replay: nonces protegidos em memória.\n" +
|
}
|
||||||
"[" + now + "] [INFO] Registro de aplicações web ativo e pronto para autorização segura.\n";
|
txtLogs.positionCaret(txtLogs.getText().length());
|
||||||
txtLogs.setText(sb);
|
txtLogs.setScrollTop(Double.MAX_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private VBox createHeaderLabel(String title, String subtitle) {
|
private VBox createHeaderLabel(String title, String subtitle) {
|
||||||
|
|||||||
+18
-3
@@ -106,9 +106,24 @@ public class SignaturePromptDialog {
|
|||||||
}
|
}
|
||||||
addField(grid, row++, "Operação:", "Assinatura Digital (Digest)");
|
addField(grid, row++, "Operação:", "Assinatura Digital (Digest)");
|
||||||
}
|
}
|
||||||
addField(grid, row++, "Certificado:", cert.commonName() != null ? cert.commonName() : cert.alias());
|
String rawCertName = cert.commonName() != null ? cert.commonName() : cert.alias();
|
||||||
addField(grid, row++, "Tipo / Dispositivo:", cert.type().getCode() + " • " +
|
String holderName = rawCertName.contains(":") ? rawCertName.split(":")[0].trim() : rawCertName.trim();
|
||||||
(cert.hardwareDeviceName() != null ? cert.hardwareDeviceName() : cert.type().getLabel()));
|
String docInfo = "";
|
||||||
|
if (cert.cnpj() != null && !cert.cnpj().isBlank()) {
|
||||||
|
docInfo = ", CNPJ " + cert.cnpj();
|
||||||
|
} else if (cert.cpf() != null && !cert.cpf().isBlank()) {
|
||||||
|
docInfo = ", CPF " + cert.cpf();
|
||||||
|
} else if (rawCertName.contains(":")) {
|
||||||
|
String candidate = rawCertName.split(":")[1].trim();
|
||||||
|
if (candidate.length() == 14) {
|
||||||
|
docInfo = ", CNPJ " + candidate;
|
||||||
|
} else if (candidate.length() == 11) {
|
||||||
|
docInfo = ", CPF " + candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String certDesc = holderName + docInfo + " [" + cert.type().getCode() + "]";
|
||||||
|
addField(grid, row++, "Certificado:", certDesc);
|
||||||
|
addField(grid, row++, "Tipo:", cert.type().getCode() + " (" + cert.type().getLabel() + ")");
|
||||||
addField(grid, row++, "Algoritmo:", context.algorithm());
|
addField(grid, row++, "Algoritmo:", context.algorithm());
|
||||||
|
|
||||||
detailsBox.getChildren().add(grid);
|
detailsBox.getChildren().add(grid);
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 54" width="36" height="54">
|
||||||
|
<!-- Escudo Criptográfico Vetorial ICP-Brasil -->
|
||||||
|
<path d="M18 4 L32 9 V24 C32 37 25 46 18 50 C11 46 4 37 4 24 V9 Z" fill="#0d47a1" opacity="0.14"/>
|
||||||
|
<path d="M18 4 L32 9 V24 C32 37 25 46 18 50 C11 46 4 37 4 24 V9 Z" fill="none" stroke="#0d47a1" stroke-width="2.2" stroke-linejoin="round"/>
|
||||||
|
|
||||||
|
<!-- Corpo do Cadeado de Segurança -->
|
||||||
|
<rect x="11" y="24" width="14" height="12" rx="2.5" fill="#0284c7"/>
|
||||||
|
|
||||||
|
<!-- Arco Superior do Cadeado -->
|
||||||
|
<path d="M13.5 24 V19 C13.5 16.5 15.5 14.5 18 14.5 C20.5 14.5 22.5 16.5 22.5 19 V24" fill="none" stroke="#0284c7" stroke-width="2.2" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- Orifício da Chave -->
|
||||||
|
<circle cx="18" cy="29" r="1.6" fill="#ffffff"/>
|
||||||
|
<path d="M17.3 29.5 L18.7 29.5 L19.2 33 L16.8 33 Z" fill="#ffffff"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 858 B |
+82
-29
@@ -107,6 +107,7 @@
|
|||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
background: linear-gradient(to right, #ffffff, #cbd5e1);
|
background: linear-gradient(to right, #ffffff, #cbd5e1);
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -671,11 +672,8 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Tema do Selo Visual:</label>
|
<label class="form-label">Tema do Selo Visual:</label>
|
||||||
<select id="pdfSealThemeSelect" class="cert-bar-select">
|
<select id="pdfSealThemeSelect" class="cert-bar-select">
|
||||||
<option value="icp-brasil">ICP-Brasil Institucional (Padrão)</option>
|
<option value="icp-brasil">Selo Padrão ICP-Brasil (Oficial)</option>
|
||||||
<option value="gov-br">Governamental (Gov.br / ITI)</option>
|
<option value="custom">🎨 Selo Personalizado do Designer</option>
|
||||||
<option value="cybercore-modern">CyberCore Moderno</option>
|
|
||||||
<option value="minimal-clean">Minimalista Clássico</option>
|
|
||||||
<option value="custom">🎨 Selo Customizado do Designer</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1031,7 +1029,31 @@
|
|||||||
certs.forEach((c) => {
|
certs.forEach((c) => {
|
||||||
const opt = document.createElement('option');
|
const opt = document.createElement('option');
|
||||||
opt.value = c.id;
|
opt.value = c.id;
|
||||||
opt.textContent = `${c.common_name || c.alias} [${c.type}] (${c.device_name || c.provider_id})`;
|
|
||||||
|
let rawName = c.common_name || c.alias || 'Certificado';
|
||||||
|
let displayName = rawName;
|
||||||
|
let docNum = c.cpf || c.cnpj || c.cpf_cnpj || '';
|
||||||
|
|
||||||
|
if (rawName.includes(':')) {
|
||||||
|
const parts = rawName.split(':');
|
||||||
|
displayName = parts[0].trim();
|
||||||
|
docNum = parts[1].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const digits = docNum ? String(docNum).replace(/\D/g, '') : '';
|
||||||
|
let docFormatted = '';
|
||||||
|
if (digits.length === 14) {
|
||||||
|
docFormatted = `CNPJ ${digits.replace(/^(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})$/, '../-')}`;
|
||||||
|
} else if (digits.length === 11) {
|
||||||
|
docFormatted = `CPF ${digits.replace(/^(\d{3})(\d{3})(\d{3})(\d{2})$/, '..-')}`;
|
||||||
|
} else if (docNum) {
|
||||||
|
docFormatted = docNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeStr = `[${c.type || 'A1'}]`;
|
||||||
|
const text = docFormatted ? `${displayName}, ${docFormatted} ${typeStr}` : `${displayName} ${typeStr}`;
|
||||||
|
opt.textContent = text;
|
||||||
certSelect.appendChild(opt);
|
certSelect.appendChild(opt);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1054,9 +1076,25 @@
|
|||||||
certValidityText.textContent = `Validade: ${cert.valid_to ? new Date(cert.valid_to).toLocaleDateString() : 'N/D'}`;
|
certValidityText.textContent = `Validade: ${cert.valid_to ? new Date(cert.valid_to).toLocaleDateString() : 'N/D'}`;
|
||||||
|
|
||||||
if (pdfStampPicker) {
|
if (pdfStampPicker) {
|
||||||
|
const rawDoc = cert.cpf || cert.cnpj || cert.cpf_cnpj || '';
|
||||||
|
const digits = String(rawDoc).replace(/\D/g, '');
|
||||||
|
let docStr = '';
|
||||||
|
if (digits.length === 14) {
|
||||||
|
docStr = `CNPJ: ${digits.replace(/^(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})$/, '../-')}`;
|
||||||
|
} else if (digits.length === 11) {
|
||||||
|
docStr = `CPF: ${digits.replace(/^(\d{3})(\d{3})(\d{3})(\d{2})$/, '..-')}`;
|
||||||
|
} else {
|
||||||
|
docStr = cert.type === 'A3' ? 'Token Físico A3' : 'ICP-Brasil';
|
||||||
|
}
|
||||||
|
|
||||||
|
let cleanSignerName = cert.common_name || cert.alias || 'Titular';
|
||||||
|
if (cleanSignerName && cleanSignerName.includes(':')) {
|
||||||
|
cleanSignerName = cleanSignerName.split(':')[0].trim();
|
||||||
|
}
|
||||||
|
|
||||||
pdfStampPicker.updateSealTheme(null, {
|
pdfStampPicker.updateSealTheme(null, {
|
||||||
signerName: cert.common_name || cert.alias,
|
signerName: cleanSignerName,
|
||||||
signerDoc: cert.cpf_cnpj ? `CPF: ${cert.cpf_cnpj}` : (cert.type === 'A3' ? 'Token Físico A3' : 'ICP-Brasil')
|
signerDoc: docStr
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1209,10 +1247,15 @@
|
|||||||
// Sincroniza alteração de tema com o selo sobre o PDF
|
// Sincroniza alteração de tema com o selo sobre o PDF
|
||||||
pdfSealThemeSelect.addEventListener('change', () => {
|
pdfSealThemeSelect.addEventListener('change', () => {
|
||||||
const themeVal = pdfSealThemeSelect.value;
|
const themeVal = pdfSealThemeSelect.value;
|
||||||
if (pdfStampPicker) {
|
if (themeVal === 'custom') {
|
||||||
if (themeVal === 'custom' && currentCustomSealConfig) {
|
if (!currentCustomSealConfig && typeof pdfSealDesigner !== 'undefined' && pdfSealDesigner) {
|
||||||
|
currentCustomSealConfig = pdfSealDesigner.getConfig();
|
||||||
|
}
|
||||||
|
if (pdfStampPicker && currentCustomSealConfig) {
|
||||||
pdfStampPicker.updateSealTheme(currentCustomSealConfig);
|
pdfStampPicker.updateSealTheme(currentCustomSealConfig);
|
||||||
} else {
|
}
|
||||||
|
} else {
|
||||||
|
if (pdfStampPicker) {
|
||||||
pdfStampPicker.updateSealTheme(themeVal);
|
pdfStampPicker.updateSealTheme(themeVal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1230,21 +1273,29 @@
|
|||||||
const stampConfig = pdfStampPicker ? pdfStampPicker.getStampConfig() : { page: 1, x: 50, y: 50, scale: 1.0 };
|
const stampConfig = pdfStampPicker ? pdfStampPicker.getStampConfig() : { page: 1, x: 50, y: 50, scale: 1.0 };
|
||||||
const selectedTheme = pdfSealThemeSelect.value;
|
const selectedTheme = pdfSealThemeSelect.value;
|
||||||
|
|
||||||
|
let effectiveCustomSeal = null;
|
||||||
|
if (selectedTheme === 'custom') {
|
||||||
|
if (currentCustomSealConfig) {
|
||||||
|
effectiveCustomSeal = currentCustomSealConfig;
|
||||||
|
} else if (typeof pdfSealDesigner !== 'undefined' && pdfSealDesigner) {
|
||||||
|
effectiveCustomSeal = pdfSealDesigner.getConfig();
|
||||||
|
currentCustomSealConfig = effectiveCustomSeal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const signParams = {
|
const signParams = {
|
||||||
certificateId: selectedCertificateId,
|
certificateId: selectedCertificateId,
|
||||||
pdf: currentPdfFile || currentPdfBytes,
|
pdf: currentPdfFile || currentPdfBytes,
|
||||||
page: stampConfig.page,
|
page: stampConfig.page,
|
||||||
x: stampConfig.x,
|
x: stampConfig.x,
|
||||||
y: stampConfig.y,
|
y: stampConfig.y,
|
||||||
scale: stampConfig.scale
|
scale: stampConfig.scale,
|
||||||
|
sealCustom: effectiveCustomSeal,
|
||||||
|
seal_custom: effectiveCustomSeal,
|
||||||
|
sealThemeId: (selectedTheme === 'custom' ? null : selectedTheme),
|
||||||
|
seal_theme_id: (selectedTheme === 'custom' ? null : selectedTheme)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (selectedTheme === 'custom' && currentCustomSealConfig) {
|
|
||||||
signParams.seal_custom = currentCustomSealConfig;
|
|
||||||
} else {
|
|
||||||
signParams.seal_theme_id = selectedTheme === 'custom' ? 'icp-brasil' : selectedTheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await signer.signPdfDocument(signParams);
|
const result = await signer.signPdfDocument(signParams);
|
||||||
|
|
||||||
pdfAlert.className = 'alert alert-success';
|
pdfAlert.className = 'alert alert-success';
|
||||||
@@ -1322,22 +1373,24 @@
|
|||||||
const mount = document.getElementById('sealDesignerMount');
|
const mount = document.getElementById('sealDesignerMount');
|
||||||
pdfSealDesigner = new PdfSealDesigner({
|
pdfSealDesigner = new PdfSealDesigner({
|
||||||
container: mount,
|
container: mount,
|
||||||
initialConfig: {
|
initialTheme: {
|
||||||
name: 'Selo Customizado',
|
name: 'Selo Customizado',
|
||||||
title: 'ASSINADO DIGITALMENTE',
|
customHeader: 'DOCUMENTO ASSINADO DIGITALMENTE',
|
||||||
primary_color: '#0284c7',
|
primaryColor: '#0284c7',
|
||||||
secondary_color: '#10b981',
|
secondaryColor: '#10b981',
|
||||||
background_color: '#ffffff',
|
backgroundColor: '#ffffff',
|
||||||
text_color: '#0f172a',
|
textColor: '#0f172a',
|
||||||
border_color: '#38bdf8',
|
borderColor: '#38bdf8',
|
||||||
icon_style: 'shield',
|
iconStyle: 'shield',
|
||||||
default_cpf_mode: 'MASKED',
|
defaultCpfMode: 'MASKED',
|
||||||
default_include_date_time: true
|
defaultIncludeDateTime: true
|
||||||
},
|
},
|
||||||
onConfigChange: (cfg) => {
|
onChange: (cfg) => {
|
||||||
currentCustomSealConfig = cfg;
|
currentCustomSealConfig = cfg;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Inicializa currentCustomSealConfig com o estado atual do designer
|
||||||
|
currentCustomSealConfig = pdfSealDesigner.getConfig();
|
||||||
|
|
||||||
// Botão para aplicar o selo na aba de PDF
|
// Botão para aplicar o selo na aba de PDF
|
||||||
const applyBtnWrap = document.createElement('div');
|
const applyBtnWrap = document.createElement('div');
|
||||||
|
|||||||
@@ -20,7 +20,31 @@ export class PdfSealDesigner {
|
|||||||
throw new Error('Container DOM obrigatório para instanciar PdfSealDesigner.');
|
throw new Error('Container DOM obrigatório para instanciar PdfSealDesigner.');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onChange = options.onChange || null;
|
this.onChange = options.onChange || options.onConfigChange || null;
|
||||||
|
|
||||||
|
const rawInitial = options.initialTheme || options.initialConfig || {};
|
||||||
|
const normalizedInitial = {
|
||||||
|
name: rawInitial.name,
|
||||||
|
description: rawInitial.description,
|
||||||
|
width: rawInitial.width,
|
||||||
|
height: rawInitial.height,
|
||||||
|
primaryColor: rawInitial.primaryColor || rawInitial.primary_color,
|
||||||
|
secondaryColor: rawInitial.secondaryColor || rawInitial.secondary_color,
|
||||||
|
backgroundColor: rawInitial.backgroundColor || rawInitial.background_color,
|
||||||
|
borderColor: rawInitial.borderColor || rawInitial.border_color,
|
||||||
|
textColor: rawInitial.textColor || rawInitial.text_color,
|
||||||
|
iconStyle: rawInitial.iconStyle || rawInitial.icon_style,
|
||||||
|
customLogoBase64: rawInitial.customLogoBase64 || rawInitial.custom_logo_base64,
|
||||||
|
customHeader: rawInitial.customHeader || rawInitial.custom_header || rawInitial.title,
|
||||||
|
customFooter: rawInitial.customFooter || rawInitial.custom_footer,
|
||||||
|
customFieldLabel: rawInitial.customFieldLabel || rawInitial.custom_field_label,
|
||||||
|
customFieldValue: rawInitial.customFieldValue || rawInitial.custom_field_value,
|
||||||
|
defaultCpfMode: rawInitial.defaultCpfMode || rawInitial.default_cpf_mode,
|
||||||
|
defaultIncludeDateTime: rawInitial.defaultIncludeDateTime !== undefined ? rawInitial.defaultIncludeDateTime : rawInitial.default_include_date_time
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove chaves indefinidas para não sobrescrever os defaults
|
||||||
|
Object.keys(normalizedInitial).forEach(k => normalizedInitial[k] === undefined && delete normalizedInitial[k]);
|
||||||
|
|
||||||
// Estado do tema
|
// Estado do tema
|
||||||
this.theme = {
|
this.theme = {
|
||||||
@@ -37,11 +61,11 @@ export class PdfSealDesigner {
|
|||||||
customLogoBase64: null,
|
customLogoBase64: null,
|
||||||
customHeader: 'DOCUMENTO ASSINADO DIGITALMENTE',
|
customHeader: 'DOCUMENTO ASSINADO DIGITALMENTE',
|
||||||
customFooter: 'Verifique a autenticidade da assinatura eletrônica em https://validar.iti.gov.br',
|
customFooter: 'Verifique a autenticidade da assinatura eletrônica em https://validar.iti.gov.br',
|
||||||
customFieldLabel: 'Cargo',
|
customFieldLabel: '',
|
||||||
customFieldValue: 'Diretor Responsável',
|
customFieldValue: '',
|
||||||
defaultCpfMode: 'MASKED',
|
defaultCpfMode: 'MASKED',
|
||||||
defaultIncludeDateTime: true,
|
defaultIncludeDateTime: true,
|
||||||
...(options.initialTheme || {})
|
...normalizedInitial
|
||||||
};
|
};
|
||||||
|
|
||||||
this._initDom();
|
this._initDom();
|
||||||
|
|||||||
@@ -367,13 +367,13 @@ export class CyberCoreSigner {
|
|||||||
y: params.y !== undefined ? Number(params.y) : null,
|
y: params.y !== undefined ? Number(params.y) : null,
|
||||||
scale: params.scale !== undefined ? Number(params.scale) : 1.0,
|
scale: params.scale !== undefined ? Number(params.scale) : 1.0,
|
||||||
visible_seal: params.visibleSeal !== undefined ? Boolean(params.visibleSeal) : true,
|
visible_seal: params.visibleSeal !== undefined ? Boolean(params.visibleSeal) : true,
|
||||||
seal_theme_id: params.sealThemeId || params.sealTheme || null,
|
seal_theme_id: params.sealThemeId || params.seal_theme_id || params.sealTheme || null,
|
||||||
seal_custom: params.sealCustom || null,
|
seal_custom: params.sealCustom || params.seal_custom || null,
|
||||||
reason: params.reason || 'Assinatura Digital ICP-Brasil',
|
reason: params.reason || 'Assinatura Digital ICP-Brasil',
|
||||||
location: params.location || 'Brasil',
|
location: params.location || 'Brasil',
|
||||||
cpf_display_mode: params.cpfDisplayMode || null,
|
cpf_display_mode: params.cpfDisplayMode || params.cpf_display_mode || null,
|
||||||
include_date_time: params.includeDateTime !== undefined ? Boolean(params.includeDateTime) : null,
|
include_date_time: params.includeDateTime !== undefined ? Boolean(params.includeDateTime) : (params.include_date_time !== undefined ? Boolean(params.include_date_time) : null),
|
||||||
custom_extra_info: params.customExtraInfo || null,
|
custom_extra_info: params.customExtraInfo || params.custom_extra_info || null,
|
||||||
nonce: nonce,
|
nonce: nonce,
|
||||||
expires_at: expiresAt
|
expires_at: expiresAt
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user