Files
SPA-landing/symbols/js/text-generator.js
architeur 7672653254 Integrate symbols/ into main repo
- Added symbols/ folder (previously separate repo)
- Removed symbols/ from .gitignore
- Updated CLAUDE.md documentation
- Deleted separate Symbols repo on Gitea

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 20:53:14 +01:00

1155 lines
44 KiB
JavaScript

// ========== TEXT-GENERATOR v5 ==========
// Features: Separate Padding pro Kante, Reset-Button, Einzelner Pfeil-Export, Pfeilspitzenlaenge
var textGenDefaults = {
text: '',
fontSize: 16,
textColor: '#000000',
frameColor: '#000000',
shape: 'none',
frameScale: 100,
paddingTop: 10,
paddingRight: 10,
paddingBottom: 10,
paddingLeft: 10,
lineStyle: 'solid',
lineWeight: 2,
arrow: 'none',
arrowLength: 40,
arrowAngle: 0,
arrowBend: 50,
arrowSize: 10,
arrowTipLength: 15
};
var textGenState = JSON.parse(JSON.stringify(textGenDefaults));
// Custom Symbols aus localStorage
var customSymbols = [];
function initTextGenerator() {
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');
// Gesamt-Padding Input
var paddingAllInput = document.getElementById("paddingAll");
var paddingAllValueEl = document.getElementById("paddingAllValue");
// Separate Padding Inputs
var paddingTopInput = document.getElementById('paddingTop');
var paddingTopValue = document.getElementById('paddingTopValue');
var paddingRightInput = document.getElementById('paddingRight');
var paddingRightValue = document.getElementById('paddingRightValue');
var paddingBottomInput = document.getElementById('paddingBottom');
var paddingBottomValue = document.getElementById('paddingBottomValue');
var paddingLeftInput = document.getElementById('paddingLeft');
var paddingLeftValue = document.getElementById('paddingLeftValue');
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');
var arrowTipLengthInput = document.getElementById('arrowTipLength');
var arrowTipLengthValue = document.getElementById('arrowTipLengthValue');
// 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();
});
}
// Gesamt-Padding handler (setzt alle 4 Seiten)
if (paddingAllInput) {
paddingAllInput.addEventListener("input", function(e) {
var val = parseInt(e.target.value);
textGenState.paddingTop = val;
textGenState.paddingRight = val;
textGenState.paddingBottom = val;
textGenState.paddingLeft = val;
paddingAllValueEl.textContent = val + "px";
// Update einzelne Slider
if (paddingTopInput) { paddingTopInput.value = val; paddingTopValue.textContent = val + "px"; }
if (paddingRightInput) { paddingRightInput.value = val; paddingRightValue.textContent = val + "px"; }
if (paddingBottomInput) { paddingBottomInput.value = val; paddingBottomValue.textContent = val + "px"; }
if (paddingLeftInput) { paddingLeftInput.value = val; paddingLeftValue.textContent = val + "px"; }
updateTextPreview();
});
}
// Separate Padding handlers
if (paddingTopInput) {
paddingTopInput.addEventListener('input', function(e) {
textGenState.paddingTop = parseInt(e.target.value);
paddingTopValue.textContent = e.target.value + 'px';
updateTextPreview();
});
}
if (paddingRightInput) {
paddingRightInput.addEventListener('input', function(e) {
textGenState.paddingRight = parseInt(e.target.value);
paddingRightValue.textContent = e.target.value + 'px';
updateTextPreview();
});
}
if (paddingBottomInput) {
paddingBottomInput.addEventListener('input', function(e) {
textGenState.paddingBottom = parseInt(e.target.value);
paddingBottomValue.textContent = e.target.value + 'px';
updateTextPreview();
});
}
if (paddingLeftInput) {
paddingLeftInput.addEventListener('input', function(e) {
textGenState.paddingLeft = parseInt(e.target.value);
paddingLeftValue.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();
try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); }
});
}
// Arrow angle
if (arrowAngleInput) {
arrowAngleInput.addEventListener('input', function(e) {
textGenState.arrowAngle = parseInt(e.target.value);
arrowAngleValue.textContent = e.target.value + '\u00B0';
updateTextPreview();
try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); }
});
}
// Arrow bend position
if (arrowBendInput) {
arrowBendInput.addEventListener('input', function(e) {
textGenState.arrowBend = parseInt(e.target.value);
arrowBendValue.textContent = e.target.value + '%';
updateTextPreview();
try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); }
});
}
// Arrow size (width)
if (arrowSizeInput) {
arrowSizeInput.addEventListener('input', function(e) {
textGenState.arrowSize = parseInt(e.target.value);
arrowSizeValue.textContent = e.target.value + 'px';
updateTextPreview();
try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); }
});
}
// Arrow tip length (laenger = schmaler)
if (arrowTipLengthInput) {
arrowTipLengthInput.addEventListener('input', function(e) {
textGenState.arrowTipLength = parseInt(e.target.value);
arrowTipLengthValue.textContent = e.target.value + 'px';
updateTextPreview();
try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); }
});
}
// 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();
try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); }
});
});
// 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();
try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); }
});
});
// 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();
try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); }
}
// ========== RESET FUNCTION ==========
function resetToDefaults() {
textGenState = JSON.parse(JSON.stringify(textGenDefaults));
// Update all UI elements
var textInput = document.getElementById('customText');
if (textInput) textInput.value = '';
var fontSizeInput = document.getElementById('fontSize');
var fontSizeValue = document.getElementById('fontSizeValue');
if (fontSizeInput) fontSizeInput.value = textGenDefaults.fontSize;
if (fontSizeValue) fontSizeValue.textContent = textGenDefaults.fontSize + 'px';
var textColorInput = document.getElementById('textColor');
var textColorValue = document.getElementById('textColorValue');
if (textColorInput) textColorInput.value = textGenDefaults.textColor;
if (textColorValue) textColorValue.textContent = textGenDefaults.textColor;
var frameColorInput = document.getElementById('frameColor');
var frameColorValue = document.getElementById('frameColorValue');
if (frameColorInput) frameColorInput.value = textGenDefaults.frameColor;
if (frameColorValue) frameColorValue.textContent = textGenDefaults.frameColor;
var frameScaleInput = document.getElementById('frameScale');
var frameScaleValue = document.getElementById('frameScaleValue');
if (frameScaleInput) frameScaleInput.value = textGenDefaults.frameScale;
if (frameScaleValue) frameScaleValue.textContent = textGenDefaults.frameScale + '%';
// Reset Gesamt-Padding
var paddingAllInput = document.getElementById('paddingAll');
var paddingAllValue = document.getElementById('paddingAllValue');
if (paddingAllInput) paddingAllInput.value = textGenDefaults.paddingTop;
if (paddingAllValue) paddingAllValue.textContent = textGenDefaults.paddingTop + 'px';
// Reset separate padding
['Top', 'Right', 'Bottom', 'Left'].forEach(function(side) {
var input = document.getElementById('padding' + side);
var value = document.getElementById('padding' + side + 'Value');
var defaultVal = textGenDefaults['padding' + side];
if (input) input.value = defaultVal;
if (value) value.textContent = defaultVal + 'px';
});
var arrowLengthInput = document.getElementById('arrowLength');
var arrowLengthValue = document.getElementById('arrowLengthValue');
if (arrowLengthInput) arrowLengthInput.value = textGenDefaults.arrowLength;
if (arrowLengthValue) arrowLengthValue.textContent = textGenDefaults.arrowLength + 'px';
var arrowAngleInput = document.getElementById('arrowAngle');
var arrowAngleValue = document.getElementById('arrowAngleValue');
if (arrowAngleInput) arrowAngleInput.value = textGenDefaults.arrowAngle;
if (arrowAngleValue) arrowAngleValue.textContent = textGenDefaults.arrowAngle + '\u00B0';
var arrowBendInput = document.getElementById('arrowBend');
var arrowBendValue = document.getElementById('arrowBendValue');
if (arrowBendInput) arrowBendInput.value = textGenDefaults.arrowBend;
if (arrowBendValue) arrowBendValue.textContent = textGenDefaults.arrowBend + '%';
var arrowSizeInput = document.getElementById('arrowSize');
var arrowSizeValue = document.getElementById('arrowSizeValue');
if (arrowSizeInput) arrowSizeInput.value = textGenDefaults.arrowSize;
if (arrowSizeValue) arrowSizeValue.textContent = textGenDefaults.arrowSize + 'px';
var arrowTipLengthInput = document.getElementById('arrowTipLength');
var arrowTipLengthValue = document.getElementById('arrowTipLengthValue');
if (arrowTipLengthInput) arrowTipLengthInput.value = textGenDefaults.arrowTipLength;
if (arrowTipLengthValue) arrowTipLengthValue.textContent = textGenDefaults.arrowTipLength + 'px';
// Reset buttons
document.querySelectorAll('.shape-btn').forEach(function(btn) {
btn.classList.toggle('active', btn.dataset.shape === 'none');
});
document.querySelectorAll('.line-btn').forEach(function(btn) {
btn.classList.toggle('active', btn.dataset.style === 'solid');
});
document.querySelectorAll('.weight-btn').forEach(function(btn) {
btn.classList.toggle('active', btn.dataset.weight === '2');
});
document.querySelectorAll('.arrow-btn').forEach(function(btn) {
btn.classList.toggle('active', btn.dataset.arrow === 'none');
});
updateFrameScaleVisibility();
updateArrowDetailsVisibility();
updateTextPreview();
try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); }
showNotification('Auf Standard zurueckgesetzt');
}
function updateFrameScaleVisibility() {
console.log("updateFrameScaleVisibility called, shape:", textGenState.shape);
var row = document.getElementById('frameScaleRow');
var paddingAllRow = document.getElementById('framePaddingAllRow');
var paddingRow = document.getElementById('framePaddingRow');
if (row) {
row.style.display = textGenState.shape === 'none' ? 'none' : 'flex';
}
if (paddingAllRow) {
paddingAllRow.style.display = textGenState.shape === 'none' ? 'none' : 'flex';
}
if (paddingRow) {
paddingRow.style.display = textGenState.shape === 'none' ? 'none' : 'flex';
}
}
function updateArrowDetailsVisibility() {
var row = document.getElementById('arrowDetailsRow');
var row2 = document.getElementById('arrowDetailsRow2');
if (row) {
row.style.display = textGenState.arrow === 'none' ? 'none' : 'flex';
}
if (row2) {
row2.style.display = textGenState.arrow === 'none' ? 'none' : 'flex';
}
}
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 paddingTop = textGenState.paddingTop;
var paddingRight = textGenState.paddingRight;
var paddingBottom = textGenState.paddingBottom;
var paddingLeft = textGenState.paddingLeft;
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;
var arrowTipLength = textGenState.arrowTipLength;
var lines = text.split('\n');
if (lines.length === 0) lines = ['Text'];
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;
var charWidth = fontSize * 0.6;
var lineHeight = fontSize * 1.3;
var textWidth = maxLineLength * charWidth;
var textHeight = lines.length * lineHeight;
var frameWidth = (textWidth + paddingLeft + paddingRight) * frameScale;
var frameHeight = (textHeight + paddingTop + paddingBottom) * frameScale;
if (frameWidth < 40) frameWidth = 40;
if (frameHeight < 30) frameHeight = 30;
var arrowSpace = (arrow !== 'none') ? arrowLength + arrowTipLength + 5 : 0;
var svgWidth = frameWidth;
var svgHeight = frameHeight;
var offsetX = 0, 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 frameCx = offsetX + frameWidth / 2;
var frameCy = offsetY + frameHeight / 2;
// Text-Verschiebung durch asymmetrisches Padding
var textOffsetX = ((paddingLeft - paddingRight) / 2) * frameScale;
var textOffsetY = ((paddingTop - paddingBottom) / 2) * frameScale;
var textCx = frameCx + textOffsetX;
var textCy = frameCy + textOffsetY;
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);
shapeSvg = '<rect x="' + (frameCx - squareSize/2) + '" y="' + (frameCy - squareSize/2) + '" 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="' + frameCx + '" cy="' + frameCy + '" r="' + (radius - lineWeight/2) + '" fill="none" stroke="' + frameColor + '" stroke-width="' + lineWeight + '"' + dashAttr + '/>';
break;
case 'oval':
shapeSvg = '<ellipse cx="' + frameCx + '" cy="' + frameCy + '" rx="' + (shapeW/2 - lineWeight/2) + '" ry="' + (shapeH/2 - lineWeight/2) + '" fill="none" stroke="' + frameColor + '" stroke-width="' + lineWeight + '"' + dashAttr + '/>';
break;
case 'diamond':
var halfW = shapeW / 2, halfH = shapeH / 2;
shapeSvg = '<polygon points="' + frameCx + ',' + (frameCy - halfH) + ' ' + (frameCx + halfW) + ',' + frameCy + ' ' + frameCx + ',' + (frameCy + halfH) + ' ' + (frameCx - halfW) + ',' + frameCy + '" fill="none" stroke="' + frameColor + '" stroke-width="' + lineWeight + '"' + dashAttr + '/>';
break;
}
var arrowSvg = '';
if (arrow !== 'none') {
arrowSvg = generateArrowPath(arrow, frameCx, frameCy, frameWidth, frameHeight, offsetX, offsetY, arrowLength, arrowAngle, arrowBend, frameColor, lineWeight, dashAttr, arrowSize, arrowTipLength);
}
var textSvg = '<text x="' + textCx + '" font-family="Arial, sans-serif" font-size="' + fontSize + '" fill="' + textColor + '" text-anchor="middle">';
var totalTextHeight = lines.length * lineHeight;
var startY = textCy - (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="' + textCx + '" 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, tipLength) {
var startX, startY, midX, midY, endX, endY;
var angleRad = angle * Math.PI / 180;
// Startpunkt am Rahmen
switch(direction) {
case 'top':
startX = cx;
startY = offsetY;
midX = startX;
midY = startY - length * bendPos;
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) {
pathD = 'M ' + startX + ' ' + startY + ' L ' + midX + ' ' + midY + ' L ' + endX + ' ' + endY;
} else {
pathD = 'M ' + startX + ' ' + startY + ' L ' + endX + ' ' + endY;
}
// Pfeilspitze: Richtung basiert auf dem LETZTEN Segment
var lastSegmentStartX, lastSegmentStartY;
if (angle !== 0 && bendPos > 0 && bendPos < 1) {
lastSegmentStartX = midX;
lastSegmentStartY = midY;
} else {
lastSegmentStartX = startX;
lastSegmentStartY = startY;
}
// Winkel des letzten Segments berechnen
var arrowHeadAngle = Math.atan2(endY - lastSegmentStartY, endX - lastSegmentStartX);
// Pfeilspitze Punkte - tipLength bestimmt wie lang (und damit schmaler) die Spitze ist
// arrowSize bestimmt die Breite an der Basis
// Je laenger tipLength, desto spitzer/schmaler wird der Pfeil
var tipHalfWidth = arrowSize / 2;
// Basis der Pfeilspitze (zurueck von der Spitze)
var baseX = endX - tipLength * Math.cos(arrowHeadAngle);
var baseY = endY - tipLength * Math.sin(arrowHeadAngle);
// Seitenpunkte der Pfeilspitze
var perpAngle = arrowHeadAngle + Math.PI / 2;
var ah1x = baseX + tipHalfWidth * Math.cos(perpAngle);
var ah1y = baseY + tipHalfWidth * Math.sin(perpAngle);
var ah2x = baseX - tipHalfWidth * Math.cos(perpAngle);
var ah2y = baseY - tipHalfWidth * Math.sin(perpAngle);
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;
}
// ========== STANDALONE ARROW (Einzelner loser Pfeil) ==========
function generateStandaloneArrowSVG() {
var length = textGenState.arrowLength;
var angle = textGenState.arrowAngle;
var bendPos = textGenState.arrowBend / 100;
var color = textGenState.frameColor;
var weight = textGenState.lineWeight;
var lineStyle = textGenState.lineStyle;
var arrowSize = textGenState.arrowSize;
var tipLength = textGenState.arrowTipLength;
var dashArray = getStrokeDasharray(lineStyle);
var dashAttr = dashArray !== 'none' ? ' stroke-dasharray="' + dashArray + '"' : '';
// Padding fuer SVG
var padding = Math.max(arrowSize, tipLength) + 5;
var angleRad = angle * Math.PI / 180;
// Pfeil geht von links nach rechts
var startX = padding;
var startY = padding + length / 2;
// Knickpunkt
var midX = startX + length * bendPos;
var midY = startY;
// Endpunkt mit Winkel
var endX, endY;
if (angle !== 0 && bendPos > 0 && bendPos < 1) {
endX = midX + Math.cos(angleRad) * (length * (1 - bendPos));
endY = midY - Math.sin(angleRad) * (length * (1 - bendPos));
} else {
endX = startX + length;
endY = startY;
}
// SVG Dimensionen berechnen
var minX = Math.min(startX, midX, endX) - padding;
var maxX = Math.max(startX, midX, endX) + padding + tipLength;
var minY = Math.min(startY, midY, endY) - padding - arrowSize;
var maxY = Math.max(startY, midY, endY) + padding + arrowSize;
var svgWidth = maxX - minX;
var svgHeight = maxY - minY;
// Offset korrigieren
var offsetX = -minX;
var offsetY = -minY;
startX += offsetX;
startY += offsetY;
midX += offsetX;
midY += offsetY;
endX += offsetX;
endY += offsetY;
// Pfad erstellen
var pathD;
if (angle !== 0 && bendPos > 0 && bendPos < 1) {
pathD = 'M ' + startX + ' ' + startY + ' L ' + midX + ' ' + midY + ' L ' + endX + ' ' + endY;
} else {
pathD = 'M ' + startX + ' ' + startY + ' L ' + endX + ' ' + endY;
}
// Pfeilspitze
var lastSegmentStartX, lastSegmentStartY;
if (angle !== 0 && bendPos > 0 && bendPos < 1) {
lastSegmentStartX = midX;
lastSegmentStartY = midY;
} else {
lastSegmentStartX = startX;
lastSegmentStartY = startY;
}
var arrowHeadAngle = Math.atan2(endY - lastSegmentStartY, endX - lastSegmentStartX);
var tipHalfWidth = arrowSize / 2;
var baseX = endX - tipLength * Math.cos(arrowHeadAngle);
var baseY = endY - tipLength * Math.sin(arrowHeadAngle);
var perpAngle = arrowHeadAngle + Math.PI / 2;
var ah1x = baseX + tipHalfWidth * Math.cos(perpAngle);
var ah1y = baseY + tipHalfWidth * Math.sin(perpAngle);
var ah2x = baseX - tipHalfWidth * Math.cos(perpAngle);
var ah2y = baseY - tipHalfWidth * Math.sin(perpAngle);
var arrowHead = '<polygon points="' + endX + ',' + endY + ' ' + ah1x + ',' + ah1y + ' ' + ah2x + ',' + ah2y + '" fill="' + color + '"/>';
var pathSvg = '<path d="' + pathD + '" fill="none" stroke="' + color + '" stroke-width="' + weight + '"' + dashAttr + '/>';
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + svgWidth + ' ' + svgHeight + '" width="' + svgWidth + '" height="' + svgHeight + '">' + pathSvg + arrowHead + '</svg>';
}
function updateStandaloneArrowPreview() {
// Nur anzeigen wenn Pfeil ausgewaehlt
if (textGenState.arrow === "none") {
var preview = document.getElementById("standaloneArrowPreview");
if (preview) preview.innerHTML = '<p style="color:#999;font-size:12px;text-align:center;">Waehle einen Pfeil aus</p>';
return;
}
var preview = document.getElementById('standaloneArrowPreview');
if (preview) {
preview.innerHTML = generateStandaloneArrowSVG();
}
}
async function copyStandaloneArrow() {
var svg = generateStandaloneArrowSVG();
try {
var canvas = await svgToCanvas(svg, 2);
canvas.toBlob(async function(blob) {
try {
await navigator.clipboard.write([
new ClipboardItem({ 'image/png': blob })
]);
showNotification('Pfeil in Zwischenablage kopiert!');
} catch (err) {
downloadStandaloneArrowPNG();
}
}, 'image/png');
} catch (err) {
showNotification('Fehler beim Kopieren', 'error');
}
}
function downloadStandaloneArrowSVG() {
var svg = generateStandaloneArrowSVG();
var blob = new Blob([svg], { type: 'image/svg+xml' });
var url = URL.createObjectURL(blob);
var link = document.createElement('a');
link.href = url;
link.download = 'pfeil.svg';
link.click();
URL.revokeObjectURL(url);
showNotification('Pfeil SVG heruntergeladen!');
}
async function downloadStandaloneArrowPNG() {
var svg = generateStandaloneArrowSVG();
try {
var canvas = await svgToCanvas(svg, 2);
canvas.toBlob(function(blob) {
var link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'pfeil.png';
link.click();
URL.revokeObjectURL(link.href);
showNotification('Pfeil PNG heruntergeladen!');
}, 'image/png');
} catch (err) {
showNotification('Fehler beim PNG-Export', 'error');
}
}
async function downloadStandaloneArrowJPG() {
var svg = generateStandaloneArrowSVG();
try {
var canvas = await svgToCanvas(svg, 2);
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);
link.download = 'pfeil.jpg';
link.click();
URL.revokeObjectURL(link.href);
showNotification('Pfeil JPG heruntergeladen!');
}, 'image/jpeg', 0.95);
} catch (err) {
showNotification('Fehler beim JPG-Export', 'error');
}
}
function downloadStandaloneArrowDXF() {
var svg = generateStandaloneArrowSVG();
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;
link.download = 'pfeil.dxf';
link.click();
URL.revokeObjectURL(url);
showNotification('Pfeil DXF heruntergeladen!');
}
function addStandaloneArrowToLegend() {
var svg = generateStandaloneArrowSVG();
legendItems.push({
id: 'arrow_' + Date.now(),
name: 'Pfeil',
svg: svg,
description: ''
});
updateLegendCount();
saveLegendToStorage();
showNotification('Pfeil zur Legende hinzugefuegt!');
}
function updateTextPreview() {
console.log("updateTextPreview called");
var preview = document.getElementById('textPreview');
if (preview) {
var svg = generateTextSVG(); console.log("Generated SVG length:", svg.length); preview.innerHTML = svg;
}
}
// 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);
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');
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 STORAGE ==========
function loadCustomSymbols() {
try {
var stored = localStorage.getItem('customSymbols');
if (stored) {
customSymbols = JSON.parse(stored);
}
} catch (e) {
console.error('Fehler beim Laden der eigenen Symbole:', e);
customSymbols = [];
}
}
function saveCustomSymbols() {
try {
localStorage.setItem('customSymbols', JSON.stringify(customSymbols));
} catch (e) {
console.error('Fehler beim Speichern der eigenen Symbole:', e);
}
}
function openSaveModal() {
var modal = document.getElementById('saveModal');
var nameInput = document.getElementById('symbolName');
var descInput = document.getElementById('symbolDescription');
var suggestedName = textGenState.text ? textGenState.text.replace(/\n/g, ' ').substring(0, 30) : '';
nameInput.value = suggestedName;
descInput.value = '';
modal.style.display = 'flex';
nameInput.focus();
}
function closeSaveModal() {
var modal = document.getElementById('saveModal');
modal.style.display = 'none';
}
function saveCustomSymbol() {
var nameInput = document.getElementById('symbolName');
var descInput = document.getElementById('symbolDescription');
var name = nameInput.value.trim();
if (!name) {
showNotification('Bitte einen Namen eingeben!', 'error');
return;
}
var svg = generateTextSVG();
var newSymbol = {
id: 'custom_' + Date.now(),
name: name,
description: descInput.value.trim(),
svg: svg,
category: 'custom',
tags: ['eigene', 'custom', name.toLowerCase()],
createdAt: new Date().toISOString(),
state: JSON.parse(JSON.stringify(textGenState))
};
customSymbols.push(newSymbol);
saveCustomSymbols();
closeSaveModal();
showNotification('Symbol "' + name + '" gespeichert!');
var activeFilter = document.querySelector('.filter-pill.active');
if (activeFilter && activeFilter.dataset.filter === 'custom') {
renderSymbolGrid();
}
}
function deleteCustomSymbol(id) {
if (!confirm('Symbol wirklich loeschen?')) return;
customSymbols = customSymbols.filter(function(s) { return s.id !== id; });
saveCustomSymbols();
renderSymbolGrid();
showNotification('Symbol geloescht');
}
var originalRenderSymbolGrid = typeof renderSymbolGrid === 'function' ? renderSymbolGrid : null;
function renderSymbolGridWithCustom() {
var activeFilter = document.querySelector('.filter-pill.active');
var filter = activeFilter ? activeFilter.dataset.filter : 'all';
if (filter === 'custom') {
renderCustomSymbolsOnly();
} else if (originalRenderSymbolGrid) {
originalRenderSymbolGrid();
if (filter === 'all') {
appendCustomSymbols();
}
}
}
function renderCustomSymbolsOnly() {
var grid = document.getElementById('symbolGrid');
if (!grid) return;
grid.innerHTML = '';
if (customSymbols.length === 0) {
grid.innerHTML = '<div class="no-results">Noch keine eigenen Symbole gespeichert.<br>Erstelle ein Text-Symbol und klicke auf "Speichern".</div>';
return;
}
customSymbols.forEach(function(symbol) {
var card = createCustomSymbolCard(symbol);
grid.appendChild(card);
});
}
function appendCustomSymbols() {
var grid = document.getElementById('symbolGrid');
if (!grid || customSymbols.length === 0) return;
customSymbols.forEach(function(symbol) {
var card = createCustomSymbolCard(symbol);
grid.appendChild(card);
});
}
function createCustomSymbolCard(symbol) {
var card = document.createElement('div');
card.className = 'symbol-card';
card.innerHTML =
'<button class="btn-delete" onclick="event.stopPropagation(); deleteCustomSymbol(\'' + symbol.id + '\')" title="Loeschen">X</button>' +
'<div class="symbol-preview">' + symbol.svg + '</div>' +
'<div class="symbol-name">' + escapeHtml(symbol.name) + '</div>' +
'<div class="symbol-actions">' +
'<button class="btn-action btn-copy" onclick="copySymbolAsImage(\'' + symbol.id + '\', true)" title="Kopieren">Kopieren</button>' +
'<button class="btn-action btn-svg" onclick="downloadSymbolSVG(\'' + symbol.id + '\', true)" title="SVG">SVG</button>' +
'<button class="btn-action btn-dxf" onclick="downloadSymbolDXF(\'' + symbol.id + '\', true)" title="DXF">DXF</button>' +
'<button class="btn-action btn-legend" onclick="addSymbolToLegend(\'' + symbol.id + '\', true)" title="Legende">+</button>' +
'</div>';
return card;
}
function escapeHtml(text) {
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function getSymbolById(id, isCustom) {
if (isCustom) {
return customSymbols.find(function(s) { return s.id === id; });
}
return allSymbols.find(function(s) { return s.id === id; });
}
// ========== IMPRESSUM & DATENSCHUTZ ==========
function openImpressum() {
var modal = document.getElementById('impressumModal');
if (modal) modal.style.display = 'flex';
}
function closeImpressum() {
var modal = document.getElementById('impressumModal');
if (modal) modal.style.display = 'none';
}
function openDatenschutz() {
var modal = document.getElementById('datenschutzModal');
if (modal) modal.style.display = 'flex';
}
function closeDatenschutz() {
var modal = document.getElementById('datenschutzModal');
if (modal) modal.style.display = 'none';
}
// Init beim Laden
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTextGenerator);
} else {
initTextGenerator();
}
// ========== GLOBAL EXPORTS fuer onclick Handler ==========
window.resetToDefaults = resetToDefaults;
window.copyStandaloneArrow = copyStandaloneArrow;
window.downloadStandaloneArrowSVG = downloadStandaloneArrowSVG;
window.downloadStandaloneArrowPNG = downloadStandaloneArrowPNG;
window.downloadStandaloneArrowJPG = downloadStandaloneArrowJPG;
window.downloadStandaloneArrowDXF = downloadStandaloneArrowDXF;
window.addStandaloneArrowToLegend = addStandaloneArrowToLegend;
window.generateTextSVG = generateTextSVG;
window.copyTextAsImage = copyTextAsImage;
window.downloadTextSVG = downloadTextSVG;
window.downloadTextPNG = downloadTextPNG;
window.downloadTextJPG = downloadTextJPG;
window.downloadTextDXF = downloadTextDXF;
window.addTextToLegend = addTextToLegend;
window.toggleTextGenerator = toggleTextGenerator;
window.openImpressum = openImpressum;
window.closeImpressum = closeImpressum;
window.openDatenschutz = openDatenschutz;
window.closeDatenschutz = closeDatenschutz;