Refactor symbols app: cleanup and fix issues

- Removed duplicate files (index2/3/4.html, symbols.js duplicates)
- Kept index4.html as the main index.html (modular version)
- Removed old text-generator.js (replaced by modular version)
- Fixed ID mismatch in ui-bindings.js to match HTML
- Added square and circle shape support in svg-generator.js
- Added legend preview with copy functionality
- Removed 580 lines of obsolete text-generator v4 code from app.js
- Added addTextToLegend and addStandaloneArrowToLegend to export.js

Still TODO: Split large files to comply with 300 line limit
- app.js: 1219 lines
- styles.css: 1319 lines
- symbols.js: 870 lines

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
architeur
2025-12-14 21:09:39 +01:00
parent 2a50a15745
commit c0ae55a597
12 changed files with 286 additions and 4693 deletions

View File

@@ -445,6 +445,7 @@ function openLegendModal() {
const modal = document.getElementById('legendModal');
modal.classList.add('active');
renderLegendEditor();
updateLegendPreview();
}
function closeLegendModal() {
@@ -454,34 +455,38 @@ function closeLegendModal() {
function renderLegendEditor() {
const container = document.getElementById('legendItems');
if (legendItems.length === 0) {
container.innerHTML = '<div class="legend-empty">Keine Symbole in der Legende. Klicken Sie auf 📑 bei einem Symbol, um es hinzuzufügen.</div>';
updateLegendPreview();
return;
}
container.innerHTML = legendItems.map((item, index) => `
<div class="legend-item" data-index="${index}">
<div class="legend-item-preview">${item.svg}</div>
<div class="legend-item-content">
<input type="text" class="legend-item-name" value="${item.name}"
onchange="updateLegendItem(${index}, 'name', this.value)" placeholder="Name">
<input type="text" class="legend-item-desc" value="${item.description || ''}"
onchange="updateLegendItem(${index}, 'description', this.value)" placeholder="Beschreibung (optional)">
<input type="text" class="legend-item-name" value="${item.name}"
oninput="updateLegendItem(${index}, 'name', this.value)" placeholder="Name">
<input type="text" class="legend-item-desc" value="${item.description || ''}"
oninput="updateLegendItem(${index}, 'description', this.value)" placeholder="Beschreibung (optional)">
</div>
<div class="legend-item-actions">
<button onclick="moveLegendItem(${index}, -1)" title="Nach oben" ${index === 0 ? 'disabled' : ''}>▲</button>
<button onclick="moveLegendItem(${index}, 1)" title="Nach unten" ${index === legendItems.length - 1 ? 'disabled' : ''}>▼</button>
<button onclick="removeLegendItem(${index})" title="Entfernen" class="btn-remove">✕</button>
<button type="button" onclick="moveLegendItem(${index}, -1)" title="Nach oben" ${index === 0 ? 'disabled' : ''}>▲</button>
<button type="button" onclick="moveLegendItem(${index}, 1)" title="Nach unten" ${index === legendItems.length - 1 ? 'disabled' : ''}>▼</button>
<button type="button" onclick="removeLegendItem(${index})" title="Entfernen" class="btn-remove">✕</button>
</div>
</div>
`).join('');
updateLegendPreview();
}
function updateLegendItem(index, field, value) {
if (legendItems[index]) {
legendItems[index][field] = value;
saveLegendToStorage();
updateLegendPreview();
}
}
@@ -547,28 +552,130 @@ function loadLegendFromStorage() {
}
}
// ========== LEGENDE VORSCHAU ==========
function generateLegendSVG() {
if (legendItems.length === 0) return null;
const itemHeight = 50;
const width = 320;
const height = legendItems.length * itemHeight + 60;
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
<rect width="${width}" height="${height}" fill="white"/>
<text x="20" y="30" font-family="Arial" font-size="18" font-weight="bold">Legende</text>
<line x1="20" y1="40" x2="${width - 20}" y2="40" stroke="#ccc" stroke-width="1"/>`;
legendItems.forEach((item, index) => {
const y = 60 + index * itemHeight;
svg += `<g transform="translate(20, ${y})">
<g transform="scale(0.5)">${item.svg.replace(/<svg[^>]*>/, '').replace('</svg>', '')}</g>
<text x="50" y="20" font-family="Arial" font-size="14" font-weight="bold">${escapeHtml(item.name)}</text>
${item.description ? `<text x="50" y="35" font-family="Arial" font-size="11" fill="#666">${escapeHtml(item.description)}</text>` : ''}
</g>`;
});
svg += '</svg>';
return svg;
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function updateLegendPreview() {
const previewBox = document.getElementById('legendPreviewBox');
if (!previewBox) return;
if (legendItems.length === 0) {
previewBox.innerHTML = '<div class="legend-preview-empty">Keine Eintraege vorhanden</div>';
return;
}
const svg = generateLegendSVG();
if (svg) {
previewBox.innerHTML = svg;
}
}
async function copyLegendAsImage() {
if (legendItems.length === 0) {
showNotification('Legende ist leer', 'error');
return;
}
const svg = generateLegendSVG();
if (!svg) return;
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const itemHeight = 50;
const width = 320;
const height = legendItems.length * itemHeight + 60;
canvas.width = width * 2;
canvas.height = height * 2;
ctx.scale(2, 2);
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, width, height);
const img = new Image();
const svgBlob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
const url = URL.createObjectURL(svgBlob);
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
img.src = url;
});
ctx.drawImage(img, 0, 0, width, height);
URL.revokeObjectURL(url);
canvas.toBlob(async (blob) => {
try {
await navigator.clipboard.write([
new ClipboardItem({ 'image/png': blob })
]);
showNotification('Legende in Zwischenablage kopiert!');
} catch (err) {
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'legende.png';
link.click();
showNotification('Legende als PNG heruntergeladen');
}
}, 'image/png');
} catch (err) {
console.error('Fehler beim Kopieren:', err);
showNotification('Fehler beim Kopieren', 'error');
}
}
// ========== LEGENDE EXPORTIEREN ==========
function exportLegendSVG() {
if (legendItems.length === 0) {
showNotification('Legende ist leer', 'error');
return;
}
const itemHeight = 50;
const width = 400;
const height = legendItems.length * itemHeight + 60;
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
<rect width="${width}" height="${height}" fill="white"/>
<text x="20" y="30" font-family="Arial" font-size="18" font-weight="bold">Legende</text>
<line x1="20" y1="40" x2="${width - 20}" y2="40" stroke="#ccc" stroke-width="1"/>`;
legendItems.forEach((item, index) => {
const y = 60 + index * itemHeight;
svg += `<g transform="translate(20, ${y})">
<g transform="scale(0.5)">${item.svg.replace(/<svg[^>]*>/, '').replace('</svg>', '')}</g>
<text x="50" y="20" font-family="Arial" font-size="14" font-weight="bold">${item.name}</text>
${item.description ? `<text x="50" y="35" font-family="Arial" font-size="11" fill="#666">${item.description}</text>` : ''}
<text x="50" y="20" font-family="Arial" font-size="14" font-weight="bold">${escapeHtml(item.name)}</text>
${item.description ? `<text x="50" y="35" font-family="Arial" font-size="11" fill="#666">${escapeHtml(item.description)}</text>` : ''}
</g>`;
});
@@ -838,586 +945,14 @@ function parseSvgPath(d, scaleX, flipY) {
}
// ========== TEXT-GENERATOR v4 ==========
// Features: Farben, Skalierung, abknickbare Pfeile (korrigiert), Pfeilspitzengroesse, PNG/JPG, Speichern
var textGenState = {
text: '',
fontSize: 16,
textColor: '#000000',
frameColor: '#000000',
shape: 'none',
frameScale: 100,
framePadding: 10,
lineStyle: 'solid',
lineWeight: 2,
arrow: 'none',
arrowLength: 40,
arrowAngle: 0,
arrowBend: 50,
arrowSize: 10
};
// Custom Symbols aus localStorage
var customSymbols = [];
function initTextGenerator() {
// Load custom symbols
loadCustomSymbols();
var textInput = document.getElementById('customText');
var fontSizeInput = document.getElementById('fontSize');
var fontSizeValue = document.getElementById('fontSizeValue');
var textColorInput = document.getElementById('textColor');
var textColorValue = document.getElementById('textColorValue');
var frameColorInput = document.getElementById('frameColor');
var frameColorValue = document.getElementById('frameColorValue');
var frameScaleInput = document.getElementById('frameScale');
var frameScaleValue = document.getElementById('frameScaleValue');
var framePaddingInput = document.getElementById('framePadding');
var framePaddingValue = document.getElementById('framePaddingValue');
var arrowLengthInput = document.getElementById('arrowLength');
var arrowLengthValue = document.getElementById('arrowLengthValue');
var arrowAngleInput = document.getElementById('arrowAngle');
var arrowAngleValue = document.getElementById('arrowAngleValue');
var arrowBendInput = document.getElementById('arrowBend');
var arrowBendValue = document.getElementById('arrowBendValue');
var arrowSizeInput = document.getElementById('arrowSize');
var arrowSizeValue = document.getElementById('arrowSizeValue');
// Text input
if (textInput) {
textInput.addEventListener('input', function(e) {
textGenState.text = e.target.value;
updateTextPreview();
});
}
// Font size
if (fontSizeInput) {
fontSizeInput.addEventListener('input', function(e) {
textGenState.fontSize = parseInt(e.target.value);
fontSizeValue.textContent = e.target.value + 'px';
updateTextPreview();
});
}
// Text color
if (textColorInput) {
textColorInput.addEventListener('input', function(e) {
textGenState.textColor = e.target.value;
textColorValue.textContent = e.target.value;
updateTextPreview();
});
}
// Frame color
if (frameColorInput) {
frameColorInput.addEventListener('input', function(e) {
textGenState.frameColor = e.target.value;
frameColorValue.textContent = e.target.value;
updateTextPreview();
});
}
// Frame scale
if (frameScaleInput) {
frameScaleInput.addEventListener('input', function(e) {
textGenState.frameScale = parseInt(e.target.value);
frameScaleValue.textContent = e.target.value + '%';
updateTextPreview();
});
}
// Frame padding
if (framePaddingInput) {
framePaddingInput.addEventListener('input', function(e) {
textGenState.framePadding = parseInt(e.target.value);
framePaddingValue.textContent = e.target.value + 'px';
updateTextPreview();
});
}
// Arrow length
if (arrowLengthInput) {
arrowLengthInput.addEventListener('input', function(e) {
textGenState.arrowLength = parseInt(e.target.value);
arrowLengthValue.textContent = e.target.value + 'px';
updateTextPreview();
});
}
// Arrow angle
if (arrowAngleInput) {
arrowAngleInput.addEventListener('input', function(e) {
textGenState.arrowAngle = parseInt(e.target.value);
arrowAngleValue.textContent = e.target.value + '\u00B0';
updateTextPreview();
});
}
// Arrow bend position
if (arrowBendInput) {
arrowBendInput.addEventListener('input', function(e) {
textGenState.arrowBend = parseInt(e.target.value);
arrowBendValue.textContent = e.target.value + '%';
updateTextPreview();
});
}
// Arrow size
if (arrowSizeInput) {
arrowSizeInput.addEventListener('input', function(e) {
textGenState.arrowSize = parseInt(e.target.value);
arrowSizeValue.textContent = e.target.value + 'px';
updateTextPreview();
});
}
// Shape buttons
document.querySelectorAll('.shape-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
document.querySelectorAll('.shape-btn').forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
textGenState.shape = btn.dataset.shape;
updateFrameScaleVisibility();
updateTextPreview();
});
});
// Line style buttons
document.querySelectorAll('.line-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
document.querySelectorAll('.line-btn').forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
textGenState.lineStyle = btn.dataset.style;
updateTextPreview();
});
});
// Line weight buttons
document.querySelectorAll('.weight-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
document.querySelectorAll('.weight-btn').forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
textGenState.lineWeight = parseInt(btn.dataset.weight);
updateTextPreview();
});
});
// Arrow buttons
document.querySelectorAll('.arrow-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
document.querySelectorAll('.arrow-btn').forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
textGenState.arrow = btn.dataset.arrow;
updateArrowDetailsVisibility();
updateTextPreview();
});
});
updateFrameScaleVisibility();
updateArrowDetailsVisibility();
updateTextPreview();
}
function updateFrameScaleVisibility() {
var row = document.getElementById('frameScaleRow');
if (row) {
row.style.display = textGenState.shape === 'none' ? 'none' : 'flex';
}
}
function updateArrowDetailsVisibility() {
var row = document.getElementById('arrowDetailsRow');
if (row) {
row.style.display = textGenState.arrow === 'none' ? 'none' : 'flex';
}
}
// ========== TEXT-GENERATOR UI ==========
function toggleTextGenerator() {
var generator = document.querySelector('.text-generator');
generator.classList.toggle('collapsed');
}
function getStrokeDasharray(style) {
switch(style) {
case 'dashed': return '8,4';
case 'dotted': return '2,3';
default: return 'none';
}
}
function generateTextSVG() {
var text = textGenState.text || 'Text';
var fontSize = textGenState.fontSize;
var textColor = textGenState.textColor;
var frameColor = textGenState.frameColor;
var shape = textGenState.shape;
var frameScale = textGenState.frameScale / 100;
var padding = textGenState.framePadding;
var lineStyle = textGenState.lineStyle;
var lineWeight = textGenState.lineWeight;
var arrow = textGenState.arrow;
var arrowLength = textGenState.arrowLength;
var arrowAngle = textGenState.arrowAngle;
var arrowBend = textGenState.arrowBend / 100;
var arrowSize = textGenState.arrowSize;
// Zeilen aufteilen
var lines = text.split('\n');
if (lines.length === 0) lines = ['Text'];
// Berechne die laengste Zeile
var maxLineLength = 0;
for (var i = 0; i < lines.length; i++) {
if (lines[i].length > maxLineLength) {
maxLineLength = lines[i].length;
}
}
if (maxLineLength === 0) maxLineLength = 4;
// Dimensionen berechnen
var charWidth = fontSize * 0.6;
var lineHeight = fontSize * 1.3;
var textWidth = maxLineLength * charWidth;
var textHeight = lines.length * lineHeight;
// Basis-SVG-Groesse (Text + Padding)
var baseWidth = textWidth + (padding * 2);
var baseHeight = textHeight + (padding * 2);
// Rahmen mit Skalierung
var frameWidth = baseWidth * frameScale;
var frameHeight = baseHeight * frameScale;
// Mindestgroesse
if (frameWidth < 40) frameWidth = 40;
if (frameHeight < 30) frameHeight = 30;
// Pfeil-Raum berechnen (inkl. Pfeilspitze)
var arrowSpace = (arrow !== 'none') ? arrowLength + arrowSize + 5 : 0;
// SVG-Groesse mit Pfeil-Raum
var svgWidth = frameWidth;
var svgHeight = frameHeight;
var offsetX = 0;
var offsetY = 0;
if (arrow === 'left') {
svgWidth += arrowSpace;
offsetX = arrowSpace;
} else if (arrow === 'right') {
svgWidth += arrowSpace;
} else if (arrow === 'top') {
svgHeight += arrowSpace;
offsetY = arrowSpace;
} else if (arrow === 'bottom') {
svgHeight += arrowSpace;
}
var cx = offsetX + frameWidth / 2;
var cy = offsetY + frameHeight / 2;
var dashArray = getStrokeDasharray(lineStyle);
var dashAttr = dashArray !== 'none' ? ' stroke-dasharray="' + dashArray + '"' : '';
var shapeSvg = '';
var shapeX = offsetX + lineWeight / 2;
var shapeY = offsetY + lineWeight / 2;
var shapeW = frameWidth - lineWeight;
var shapeH = frameHeight - lineWeight;
switch(shape) {
case 'rect':
shapeSvg = '<rect x="' + shapeX + '" y="' + shapeY + '" width="' + shapeW + '" height="' + shapeH + '" fill="none" stroke="' + frameColor + '" stroke-width="' + lineWeight + '"' + dashAttr + '/>';
break;
case 'square':
var squareSize = Math.max(shapeW, shapeH);
var sqX = cx - squareSize / 2;
var sqY = cy - squareSize / 2;
shapeSvg = '<rect x="' + sqX + '" y="' + sqY + '" width="' + squareSize + '" height="' + squareSize + '" fill="none" stroke="' + frameColor + '" stroke-width="' + lineWeight + '"' + dashAttr + '/>';
break;
case 'circle':
var radius = Math.max(shapeW, shapeH) / 2;
shapeSvg = '<circle cx="' + cx + '" cy="' + cy + '" r="' + (radius - lineWeight / 2) + '" fill="none" stroke="' + frameColor + '" stroke-width="' + lineWeight + '"' + dashAttr + '/>';
break;
case 'oval':
var rx = shapeW / 2;
var ry = shapeH / 2;
shapeSvg = '<ellipse cx="' + cx + '" cy="' + cy + '" rx="' + (rx - lineWeight / 2) + '" ry="' + (ry - lineWeight / 2) + '" fill="none" stroke="' + frameColor + '" stroke-width="' + lineWeight + '"' + dashAttr + '/>';
break;
case 'diamond':
var halfW = shapeW / 2;
var halfH = shapeH / 2;
shapeSvg = '<polygon points="' + cx + ',' + (cy - halfH) + ' ' + (cx + halfW) + ',' + cy + ' ' + cx + ',' + (cy + halfH) + ' ' + (cx - halfW) + ',' + cy + '" fill="none" stroke="' + frameColor + '" stroke-width="' + lineWeight + '"' + dashAttr + '/>';
break;
}
// Pfeil generieren
var arrowSvg = '';
if (arrow !== 'none') {
arrowSvg = generateArrowPath(arrow, cx, cy, frameWidth, frameHeight, offsetX, offsetY, arrowLength, arrowAngle, arrowBend, frameColor, lineWeight, dashAttr, arrowSize);
}
// Text-Elemente erzeugen
var textSvg = '<text x="' + cx + '" font-family="Arial, sans-serif" font-size="' + fontSize + '" fill="' + textColor + '" text-anchor="middle">';
var totalTextHeight = lines.length * lineHeight;
var startY = cy - (totalTextHeight / 2) + (fontSize * 0.8);
for (var j = 0; j < lines.length; j++) {
var lineY = startY + (j * lineHeight);
var lineText = lines[j] || ' ';
lineText = lineText.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
textSvg += '<tspan x="' + cx + '" y="' + lineY + '">' + lineText + '</tspan>';
}
textSvg += '</text>';
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + svgWidth + ' ' + svgHeight + '" width="' + svgWidth + '" height="' + svgHeight + '">' + shapeSvg + arrowSvg + textSvg + '</svg>';
}
function generateArrowPath(direction, cx, cy, frameWidth, frameHeight, offsetX, offsetY, length, angle, bendPos, color, weight, dashAttr, arrowSize) {
var startX, startY, midX, midY, endX, endY;
var angleRad = angle * Math.PI / 180;
// Startpunkt am Rahmen
switch(direction) {
case 'top':
startX = cx;
startY = offsetY;
// Erster Teil geht gerade nach oben bis zum Knickpunkt
midX = startX;
midY = startY - length * bendPos;
// Zweiter Teil knickt ab
endX = midX + Math.sin(angleRad) * (length * (1 - bendPos));
endY = midY - Math.cos(angleRad) * (length * (1 - bendPos));
break;
case 'bottom':
startX = cx;
startY = offsetY + frameHeight;
midX = startX;
midY = startY + length * bendPos;
endX = midX + Math.sin(angleRad) * (length * (1 - bendPos));
endY = midY + Math.cos(angleRad) * (length * (1 - bendPos));
break;
case 'left':
startX = offsetX;
startY = cy;
midX = startX - length * bendPos;
midY = startY;
endX = midX - Math.cos(angleRad) * (length * (1 - bendPos));
endY = midY + Math.sin(angleRad) * (length * (1 - bendPos));
break;
case 'right':
startX = offsetX + frameWidth;
startY = cy;
midX = startX + length * bendPos;
midY = startY;
endX = midX + Math.cos(angleRad) * (length * (1 - bendPos));
endY = midY + Math.sin(angleRad) * (length * (1 - bendPos));
break;
}
// Pfad erstellen
var pathD;
if (angle !== 0 && bendPos > 0 && bendPos < 1) {
// Mit Knick
pathD = 'M ' + startX + ' ' + startY + ' L ' + midX + ' ' + midY + ' L ' + endX + ' ' + endY;
} else {
// Ohne Knick - gerade Linie
pathD = 'M ' + startX + ' ' + startY + ' L ' + endX + ' ' + endY;
}
// Pfeilspitze: Richtung basiert auf dem LETZTEN Segment
var lastSegmentStartX, lastSegmentStartY;
if (angle !== 0 && bendPos > 0 && bendPos < 1) {
// Letztes Segment ist von mid zu end
lastSegmentStartX = midX;
lastSegmentStartY = midY;
} else {
// Letztes Segment ist von start zu end
lastSegmentStartX = startX;
lastSegmentStartY = startY;
}
// Winkel des letzten Segments berechnen
var arrowHeadAngle = Math.atan2(endY - lastSegmentStartY, endX - lastSegmentStartX);
// Pfeilspitze Punkte
var ah1x = endX - arrowSize * Math.cos(arrowHeadAngle - Math.PI / 6);
var ah1y = endY - arrowSize * Math.sin(arrowHeadAngle - Math.PI / 6);
var ah2x = endX - arrowSize * Math.cos(arrowHeadAngle + Math.PI / 6);
var ah2y = endY - arrowSize * Math.sin(arrowHeadAngle + Math.PI / 6);
var arrowHead = '<polygon points="' + endX + ',' + endY + ' ' + ah1x + ',' + ah1y + ' ' + ah2x + ',' + ah2y + '" fill="' + color + '"/>';
return '<path d="' + pathD + '" fill="none" stroke="' + color + '" stroke-width="' + weight + '"' + dashAttr + '/>' + arrowHead;
}
function updateTextPreview() {
var preview = document.getElementById('textPreview');
if (preview) {
preview.innerHTML = generateTextSVG();
}
}
// Hilfsfunktion: SVG zu Canvas rendern
async function svgToCanvas(svg, scale) {
var parser = new DOMParser();
var svgDoc = parser.parseFromString(svg, 'image/svg+xml');
var svgEl = svgDoc.documentElement;
var svgWidth = parseFloat(svgEl.getAttribute('width')) || 100;
var svgHeight = parseFloat(svgEl.getAttribute('height')) || 100;
if (!scale) {
scale = Math.max(200 / Math.min(svgWidth, svgHeight), 1);
scale = Math.min(scale, 512 / Math.max(svgWidth, svgHeight));
}
var canvasWidth = Math.round(svgWidth * scale);
var canvasHeight = Math.round(svgHeight * scale);
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
var img = new Image();
var svgBlob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
var url = URL.createObjectURL(svgBlob);
await new Promise(function(resolve, reject) {
img.onload = resolve;
img.onerror = reject;
img.src = url;
});
ctx.drawImage(img, 0, 0, canvasWidth, canvasHeight);
URL.revokeObjectURL(url);
return canvas;
}
async function copyTextAsImage() {
var svg = generateTextSVG();
try {
var canvas = await svgToCanvas(svg);
canvas.toBlob(async function(blob) {
try {
await navigator.clipboard.write([
new ClipboardItem({ 'image/png': blob })
]);
showNotification('In Zwischenablage kopiert!');
} catch (err) {
var link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'text-symbol.png';
link.click();
showNotification('PNG heruntergeladen');
}
}, 'image/png');
} catch (err) {
console.error('Fehler:', err);
showNotification('Fehler beim Kopieren', 'error');
}
}
function downloadTextSVG() {
var svg = generateTextSVG();
var blob = new Blob([svg], { type: 'image/svg+xml' });
var url = URL.createObjectURL(blob);
var link = document.createElement('a');
link.href = url;
var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol';
link.download = 'text-' + filename + '.svg';
link.click();
URL.revokeObjectURL(url);
showNotification('SVG heruntergeladen!');
}
async function downloadTextPNG() {
var svg = generateTextSVG();
try {
var canvas = await svgToCanvas(svg, 2); // 2x Skalierung fuer bessere Qualitaet
canvas.toBlob(function(blob) {
var link = document.createElement('a');
link.href = URL.createObjectURL(blob);
var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol';
link.download = 'text-' + filename + '.png';
link.click();
URL.revokeObjectURL(link.href);
showNotification('PNG heruntergeladen!');
}, 'image/png');
} catch (err) {
console.error('Fehler:', err);
showNotification('Fehler beim PNG-Export', 'error');
}
}
async function downloadTextJPG() {
var svg = generateTextSVG();
try {
var canvas = await svgToCanvas(svg, 2);
var ctx = canvas.getContext('2d');
// Weisser Hintergrund fuer JPG
var tempCanvas = document.createElement('canvas');
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
var tempCtx = tempCanvas.getContext('2d');
tempCtx.fillStyle = '#FFFFFF';
tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
tempCtx.drawImage(canvas, 0, 0);
tempCanvas.toBlob(function(blob) {
var link = document.createElement('a');
link.href = URL.createObjectURL(blob);
var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol';
link.download = 'text-' + filename + '.jpg';
link.click();
URL.revokeObjectURL(link.href);
showNotification('JPG heruntergeladen!');
}, 'image/jpeg', 0.95);
} catch (err) {
console.error('Fehler:', err);
showNotification('Fehler beim JPG-Export', 'error');
}
}
function downloadTextDXF() {
var svg = generateTextSVG();
var dxf = svgToDxf(svg, 1);
var blob = new Blob([dxf], { type: 'application/dxf' });
var url = URL.createObjectURL(blob);
var link = document.createElement('a');
link.href = url;
var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol';
link.download = 'text-' + filename + '.dxf';
link.click();
URL.revokeObjectURL(url);
showNotification('DXF heruntergeladen!');
}
function addTextToLegend() {
var svg = generateTextSVG();
var text = textGenState.text || 'Text';
var displayName = text.replace(/\n/g, ' ').substring(0, 30);
legendItems.push({
id: 'custom_text_' + Date.now(),
name: displayName,
svg: svg,
description: ''
});
updateLegendCount();
saveLegendToStorage();
showNotification('Zur Legende hinzugefuegt!');
}
// Custom Symbols aus localStorage
var customSymbols = [];
// ========== CUSTOM SYMBOLS STORAGE ==========