Shape Symphony

Shape Symphony

Add Shape

Edit Shape

Add New Shape

Use a 20x20 viewbox for paths.

Shape Library

No shapes in library.

'; } ssy_data.shapeLibrary.forEach(shape => { // Add to Config List const item = document.createElement('div'); item.className = 'ssy-list-item'; let deleteBtn = ''; if (!shape.default) { deleteBtn = ``; } item.innerHTML = `${ssy_escapeHTML(shape.name)}${deleteBtn}`; ssy_shapeLibraryListEl.appendChild(item); // Add to Dashboard Toolbar const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'ssy-btn ssy-btn-primary'; btn.textContent = `Add ${shape.name}`; btn.setAttribute('onclick', `ssy_addShape('${ssy_escapeHTML(shape.name)}')`); ssy_shapeButtonsEl.appendChild(btn); }); } /** * Adds a new shape type to the library */ window.ssy_addShapeType = function(event) { event.preventDefault(); const name = ssy_newShapeNameInput.value.trim(); const path = ssy_newShapePathInput.value.trim(); if (name && path) { if (ssy_data.shapeLibrary.some(s => s.name.toLowerCase() === name.toLowerCase())) { alert('A shape with this name already exists.'); return; } ssy_data.shapeLibrary.push({ name, type: 'path', path, default: false }); ssy_renderShapeLibrary(); ssy_newShapeNameInput.value = ''; ssy_newShapePathInput.value = ''; } } /** * Deletes a custom shape type */ window.ssy_deleteShapeType = function(name) { if (confirm(`Are you sure you want to delete the "${name}" shape?`)) { ssy_data.shapeLibrary = ssy_data.shapeLibrary.filter(s => s.name !== name); ssy_renderShapeLibrary(); } } // --- TAB & NAVIGATION FUNCTIONS --- window.ssy_switchTab = function(tabIndex) { ssy_currentTabIndex = tabIndex; ssy_tabContents.forEach((content, index) => { content.classList.toggle('ssy-active', index === tabIndex); }); ssy_tabs.forEach((tab, index) => { tab.classList.toggle('ssy-active', index === tabIndex); }); ssy_updateNavButtons(); } window.ssy_navigateTabs = function(direction) { if (direction === 'next' && ssy_currentTabIndex < ssy_numTabs - 1) { ssy_switchTab(ssy_currentTabIndex + 1); } else if (direction === 'prev' && ssy_currentTabIndex > 0) { ssy_switchTab(ssy_currentTabIndex - 1); } } function ssy_updateNavButtons() { ssy_prevBtn.style.visibility = (ssy_currentTabIndex === 0) ? 'hidden' : 'visible'; ssy_nextBtn.style.visibility = (ssy_currentTabIndex === ssy_numTabs - 1) ? 'hidden' : 'visible'; } // --- PDF GENERATION --- window.ssy_generatePdf = function() { if (typeof jspdf === 'undefined' || typeof jspdf.jsPDF === 'undefined') { alert('Error: PDF generation library (jsPDF) not loaded.'); return; } try { // Deselect shape first so the outline doesn't appear in the PDF ssy_deselectAll(); const { jsPDF } = jspdf; const doc = new jsPDF(); const svgEl = document.getElementById('ssy-canvas'); const canvasEl = document.getElementById('ssy-pdf-canvas'); if (!svgEl || !canvasEl) throw new Error('SVG or Canvas element not found.'); if (svgEl.querySelectorAll('.ssy-shape').length === 0) { alert('Canvas is empty. Add some shapes first!'); return; } // 1. Serialize and draw SVG to Canvas const svgData = new XMLSerializer().serializeToString(svgEl); const img = new Image(); const svgBase64 = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgData))); img.onload = function() { const svgRect = svgEl.getBoundingClientRect(); canvasEl.width = svgRect.width * 2; // Increase resolution canvasEl.height = svgRect.height * 2; const ctx = canvasEl.getContext('2d'); ctx.clearRect(0, 0, canvasEl.width, canvasEl.height); // Draw a white background for the canvas ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, canvasEl.width, canvasEl.height); ctx.drawImage(img, 0, 0, canvasEl.width, canvasEl.height); const canvasImgData = canvasEl.toDataURL('image/png'); // 2. Build the PDF const margin = 15; const docWidth = doc.internal.pageSize.getWidth(); const imgWidth = docWidth - (margin * 2); const imgHeight = (canvasEl.height * imgWidth) / canvasEl.width; doc.setFont('helvetica', 'bold'); doc.setFontSize(20); doc.setTextColor(varGet('--ssy-primary-color', '#0073e6')); doc.text("My Shape Symphony", 105, 20, { align: 'center' }); doc.addImage(canvasImgData, 'PNG', margin, 30, imgWidth, imgHeight); // 3. Save PDF doc.save('shape_symphony.pdf'); }; img.src = svgBase64; } catch (e) { console.error("PDF Generation Error: ", e); alert("An error occurred while generating the PDF."); } } // --- UTILITY FUNCTIONS --- function varGet(varName, fallback = '#000') { try { return getComputedStyle(document.documentElement).getPropertyValue(varName).trim() || fallback; } catch (e) { return fallback; } } function ssy_escapeHTML(str) { if (!str) return ''; return str.replace(/[&<>"']/g, function(m) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[m]; }); } function ssy_getRandomColor() { const letters = '0123456789ABCDEF'; let color = '#'; for (let i = 0; i < 6; i++) { color += letters[Math.floor(Math.random() * 16)]; } return color; } // --- INITIALIZATION --- function ssy_initTool() { ssy_switchTab(0); ssy_renderShapeLibrary(); // Add canvas background listener ssy_canvas.addEventListener('click', ssy_deselectAll); // Add inspector listeners ssy_shapeFillInput.addEventListener('input', ssy_applyInspectorChanges); ssy_shapeWidthInput.addEventListener('input', ssy_applyInspectorChanges); ssy_shapeHeightInput.addEventListener('input', ssy_applyInspectorChanges); } ssy_initTool(); // Run the tool }); // End of DOMContentLoaded