/** * CyberCore Signer - PdfSealDesigner * Ferramenta visual interativa para criação, personalização e exportação * de temas de selos de assinatura digital PAdES (ICP-Brasil). */ export class PdfSealDesigner { /** * @param {Object} options Configurações iniciais do designer. * @param {HTMLElement|string} options.container Elemento onde renderizar o designer. * @param {Object} [options.initialTheme] Configuração inicial de tema. * @param {Function} [options.onChange] Callback chamado quando o tema for modificado: fn(theme). */ constructor(options = {}) { this.container = typeof options.container === 'string' ? document.querySelector(options.container) : options.container; if (!this.container) { throw new Error('Container DOM obrigatório para instanciar PdfSealDesigner.'); } this.onChange = options.onChange || null; // Estado do tema this.theme = { name: 'Meu Selo Personalizado', description: 'Selo visual corporativo customizado via CyberCore Web SDK', width: 320, height: 82, primaryColor: '#0d47a1', secondaryColor: '#0284c7', backgroundColor: '#ffffff', borderColor: '#93c5fd', textColor: '#0f172a', iconStyle: 'shield', customLogoBase64: null, customHeader: 'DOCUMENTO ASSINADO DIGITALMENTE', customFooter: 'Verifique a autenticidade da assinatura eletrônica em https://validar.iti.gov.br', customFieldLabel: 'Cargo', customFieldValue: 'Diretor Responsável', defaultCpfMode: 'MASKED', defaultIncludeDateTime: true, ...(options.initialTheme || {}) }; this._initDom(); this._attachEvents(); this._renderPreview(); } _initDom() { this.container.innerHTML = `

🎨 Criador de Selo Visual (PAdES / ICP-Brasil)

Personalize cores, brasão, logotipo próprio e regras LGPD para a estampa no PDF.

Paleta de Cores
${this.theme.primaryColor}
${this.theme.secondaryColor}
${this.theme.backgroundColor}
${this.theme.borderColor}
${this.theme.textColor}
Ícone e Emblema
Identificação & LGPD

👁️ Pré-visualização em Alta Resolução (320x82 pt)

Renderização visual idêntica à estampada no PDFBox do agente desktop.

📋 Objeto de Configuração (JSON)

{}
`; this._injectStyles(); // Cache de elementos this.root = this.container.querySelector('.cyber-designer-root'); this.previewBox = this.root.querySelector('.seal-live-preview-box'); this.jsonBox = this.root.querySelector('.theme-json-box code'); this.btnRemoveLogo = this.root.querySelector('.btn-remove-logo'); } _injectStyles() { if (document.getElementById('cyber-designer-styles')) return; const style = document.createElement('style'); style.id = 'cyber-designer-styles'; style.textContent = ` .cyber-designer-root { background: #0f172a; border: 1px solid #24304f; border-radius: 12px; padding: 20px; color: #f8fafc; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; max-width: 100%; box-sizing: border-box; overflow: hidden; } .designer-header { margin-bottom: 20px; border-bottom: 1px solid #1e293b; padding-bottom: 12px; } .designer-header h3 { font-size: 16px; font-weight: 700; color: #38bdf8; } .designer-header p { font-size: 12px; color: #94a3b8; margin-top: 4px; } .designer-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 24px; min-width: 0; } @media (max-width: 860px) { .designer-body { grid-template-columns: minmax(0, 1fr); } } .form-section-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; color: #38bdf8; margin: 14px 0 8px 0; border-top: 1px solid #1e293b; padding-top: 10px; } .form-row { margin-bottom: 10px; display: flex; flex-direction: column; gap: 4px; } .form-row label { font-size: 11px; color: #cbd5e1; } .form-row input[type="text"], .form-row select { background: #090d16; border: 1px solid #24304f; border-radius: 6px; padding: 7px 10px; color: #f8fafc; font-size: 12px; } .form-row input[type="text"]:focus, .form-row select:focus { border-color: #38bdf8; outline: none; } .form-row-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } .colors-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; } .color-picker-item { display: flex; flex-direction: column; gap: 4px; } .color-picker-item label { font-size: 10px; color: #94a3b8; } .color-input-wrap { display: flex; align-items: center; gap: 6px; background: #090d16; border: 1px solid #24304f; border-radius: 6px; padding: 3px 6px; } .color-input-wrap input[type="color"] { border: none; width: 22px; height: 22px; background: transparent; cursor: pointer; } .color-hex-text { font-size: 10px; font-family: monospace; color: #cbd5e1; } .icon-selection-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; } .radio-icon-item { display: flex; align-items: center; gap: 6px; background: #090d16; border: 1px solid #24304f; border-radius: 6px; padding: 6px 10px; font-size: 11px; cursor: pointer; } .logo-upload-row .logo-actions { display: flex; align-items: center; gap: 8px; } .btn-designer-upload { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; background: #1e293b; border: 1px solid #38bdf8; border-radius: 6px; color: #38bdf8; font-size: 11px; cursor: pointer; } .btn-remove-logo { background: transparent; border: 1px solid #f87171; color: #f87171; padding: 4px 8px; border-radius: 6px; font-size: 11px; cursor: pointer; } .checkbox-row { justify-content: flex-end; } .checkbox-label { display: flex; align-items: center; gap: 8px; font-size: 11px; cursor: pointer; margin-top: 18px; } .designer-preview-column { display: flex; flex-direction: column; gap: 16px; min-width: 0; max-width: 100%; } .preview-card, .json-export-card { background: #131c31; border: 1px solid #24304f; border-radius: 10px; padding: 16px; min-width: 0; max-width: 100%; box-sizing: border-box; } .preview-card h4, .json-export-card h4 { font-size: 12px; font-weight: 700; color: #cbd5e1; margin-bottom: 12px; } .seal-live-preview-box { background: #ffffff; border-radius: 6px; padding: 16px; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 15px rgba(0,0,0,0.3); max-width: 340px; margin: 0 auto; box-sizing: border-box; overflow: hidden; } .seal-live-preview-box svg { max-width: 100%; width: 100%; height: auto; display: block; } .preview-note { display: block; font-size: 10px; color: #64748b; margin-top: 8px; text-align: center; } .json-export-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; } .btn-copy-theme-json { background: #1e293b; border: 1px solid #24304f; color: #38bdf8; padding: 3px 8px; border-radius: 4px; font-size: 11px; cursor: pointer; } .btn-copy-theme-json:hover { background: #38bdf8; color: #0f172a; } .theme-json-box { background: #090d16; border: 1px solid #1e293b; border-radius: 6px; padding: 10px; font-family: 'JetBrains Mono', monospace; font-size: 10.5px; color: #38bdf8; max-height: 180px; overflow-y: auto; overflow-x: hidden; margin: 0; white-space: pre-wrap; word-break: break-all; word-wrap: break-word; max-width: 100%; min-width: 0; box-sizing: border-box; } .theme-json-box code { white-space: pre-wrap; word-break: break-all; word-wrap: break-word; display: block; max-width: 100%; } `; document.head.appendChild(style); } _attachEvents() { const bindInput = (selector, key, formatFn = v => v) => { this.root.querySelector(selector).addEventListener('input', e => { this.theme[key] = formatFn(e.target.value); this._onStateChange(); }); }; bindInput('.input-theme-name', 'name'); bindInput('.input-field-label', 'customFieldLabel'); bindInput('.input-field-value', 'customFieldValue'); // Cores const bindColor = (selector, key, labelSelector) => { const el = this.root.querySelector(selector); const label = this.root.querySelector(labelSelector); el.addEventListener('input', e => { this.theme[key] = e.target.value; label.textContent = e.target.value; this._onStateChange(); }); }; bindColor('.color-primary', 'primaryColor', '.color-primary + .color-hex-text'); bindColor('.color-secondary', 'secondaryColor', '.color-secondary + .color-hex-text'); bindColor('.color-bg', 'backgroundColor', '.color-bg + .color-hex-text'); bindColor('.color-border', 'borderColor', '.color-border + .color-hex-text'); bindColor('.color-text', 'textColor', '.color-text + .color-hex-text'); // Ícones this.root.querySelectorAll('input[name="iconStyle"]').forEach(radio => { radio.addEventListener('change', e => { if (e.target.checked) { this.theme.iconStyle = e.target.value; this._onStateChange(); } }); }); // Upload de logo const logoInput = this.root.querySelector('.designer-logo-file'); logoInput.addEventListener('change', async e => { if (e.target.files.length) { const file = e.target.files[0]; const reader = new FileReader(); reader.onload = ev => { this.theme.customLogoBase64 = ev.target.result; this.btnRemoveLogo.style.display = 'inline-block'; this._onStateChange(); }; reader.readAsDataURL(file); } }); this.btnRemoveLogo.addEventListener('click', () => { this.theme.customLogoBase64 = null; this.btnRemoveLogo.style.display = 'none'; logoInput.value = ''; this._onStateChange(); }); // LGPD CPF this.root.querySelector('.select-cpf-mode').addEventListener('change', e => { this.theme.defaultCpfMode = e.target.value; this._onStateChange(); }); // Data e hora this.root.querySelector('.chk-date-time').addEventListener('change', e => { this.theme.defaultIncludeDateTime = e.target.checked; this._onStateChange(); }); // Copiar JSON this.root.querySelector('.btn-copy-theme-json').addEventListener('click', () => { const config = this.getThemeConfig(); navigator.clipboard.writeText(JSON.stringify(config, null, 2)); const btn = this.root.querySelector('.btn-copy-theme-json'); btn.textContent = '✓ Copiado!'; setTimeout(() => (btn.textContent = 'Copiar JSON'), 1500); }); } _onStateChange() { this._renderPreview(); if (this.onChange) { this.onChange(this.getThemeConfig()); } } _renderPreview() { const t = this.theme; // CPF conforme a escolha let docText = 'CPF: ***.456.789-**'; if (t.defaultCpfMode === 'FULL') docText = 'CPF: 123.456.789-00'; else if (t.defaultCpfMode === 'NONE') docText = ''; const dateText = t.defaultIncludeDateTime ? '11/10/2026 18:30:00' : ''; let extraText = ''; if (t.customFieldLabel || t.customFieldValue) { const label = t.customFieldLabel ? `${t.customFieldLabel}: ` : ''; const val = t.customFieldValue || ''; extraText = `${label}${val}`.trim(); } let iconGraphic = ''; if (t.customLogoBase64) { iconGraphic = ``; } else { switch (t.iconStyle) { case 'star': iconGraphic = ` `; break; case 'crystal': iconGraphic = ` `; break; case 'notary': iconGraphic = ` `; break; case 'shield': default: iconGraphic = ` `; break; } } const svg = ` ${iconGraphic} ${t.customHeader || 'DOCUMENTO ASSINADO DIGITALMENTE'} MARIA DA SILVA ${[docText, extraText].filter(Boolean).join(' • ')} ${dateText ? `Data: ${dateText}` : ''} ${t.customFooter || 'ICP-Brasil • MP nº 2.200-2/2001'} `; this.previewBox.innerHTML = svg; // Na visualização do código, trunca se o Base64 for gigante para preservar performance da UI const displayConfig = { ...this.getThemeConfig() }; if (displayConfig.customLogoBase64 && displayConfig.customLogoBase64.length > 100) { const lenKb = (displayConfig.customLogoBase64.length / 1024).toFixed(1); displayConfig.customLogoBase64 = `${displayConfig.customLogoBase64.substring(0, 32)}... [${lenKb} KB]`; } this.jsonBox.textContent = JSON.stringify(displayConfig, null, 2); } /** * Retorna a configuração do tema pronto para uso no payload seal_custom de assinatura. */ getThemeConfig() { return { name: this.theme.name, description: this.theme.description, width: this.theme.width, height: this.theme.height, primaryColor: this.theme.primaryColor, secondaryColor: this.theme.secondaryColor, backgroundColor: this.theme.backgroundColor, borderColor: this.theme.borderColor, textColor: this.theme.textColor, iconStyle: this.theme.iconStyle, customLogoBase64: this.theme.customLogoBase64, customHeader: this.theme.customHeader, customFooter: this.theme.customFooter, customFieldLabel: this.theme.customFieldLabel, customFieldValue: this.theme.customFieldValue, defaultCpfMode: this.theme.defaultCpfMode, defaultIncludeDateTime: this.theme.defaultIncludeDateTime }; } /** * Alias conveniente para getThemeConfig(). */ getConfig() { return this.getThemeConfig(); } /** * Carrega um tema para edição. */ loadTheme(theme) { this.theme = { ...this.theme, ...theme }; this._initDom(); this._attachEvents(); this._renderPreview(); } } export default PdfSealDesigner;