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>
This commit is contained in:
371
symbols/js/text-generator/svg-generator.js
Normal file
371
symbols/js/text-generator/svg-generator.js
Normal file
@@ -0,0 +1,371 @@
|
||||
/**
|
||||
* SVG Generator mit SVG.js
|
||||
* Erzeugt Text-Symbole mit Rahmen und Pfeilen
|
||||
*/
|
||||
|
||||
var SvgGenerator = {
|
||||
|
||||
getDashArray: function(style, weight) {
|
||||
switch(style) {
|
||||
case 'dashed': return (weight * 4) + ',' + (weight * 2);
|
||||
case 'dotted': return weight + ',' + (weight * 1.5);
|
||||
default: return null;
|
||||
}
|
||||
},
|
||||
|
||||
measureText: function(text, fontSize) {
|
||||
var lines = text.split('\n');
|
||||
var charWidth = fontSize * 0.6;
|
||||
var lineHeight = fontSize * 1.3;
|
||||
|
||||
var maxLength = 0;
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
if (lines[i].length > maxLength) maxLength = lines[i].length;
|
||||
}
|
||||
if (maxLength === 0) maxLength = 4;
|
||||
|
||||
return {
|
||||
lines: lines,
|
||||
width: maxLength * charWidth,
|
||||
height: lines.length * lineHeight,
|
||||
lineHeight: lineHeight,
|
||||
charWidth: charWidth
|
||||
};
|
||||
},
|
||||
|
||||
createShape: function(draw, shape, x, y, width, height, style) {
|
||||
var shapeEl;
|
||||
var halfW = width / 2;
|
||||
var halfH = height / 2;
|
||||
var cx = x + halfW;
|
||||
var cy = y + halfH;
|
||||
|
||||
switch(shape) {
|
||||
case 'rect':
|
||||
shapeEl = draw.rect(width, height).move(x, y).radius(4);
|
||||
break;
|
||||
case 'rounded':
|
||||
shapeEl = draw.rect(width, height).move(x, y).radius(Math.min(halfW, halfH) * 0.5);
|
||||
break;
|
||||
case 'oval':
|
||||
shapeEl = draw.ellipse(width, height).center(cx, cy);
|
||||
break;
|
||||
case 'diamond':
|
||||
shapeEl = draw.polygon([
|
||||
[cx, y],
|
||||
[x + width, cy],
|
||||
[cx, y + height],
|
||||
[x, cy]
|
||||
]);
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (shapeEl) {
|
||||
shapeEl.fill('none').stroke({ color: style.color, width: style.weight });
|
||||
if (style.dashArray) {
|
||||
shapeEl.attr('stroke-dasharray', style.dashArray);
|
||||
}
|
||||
}
|
||||
return shapeEl;
|
||||
},
|
||||
|
||||
calculateArrowHead: function(endX, endY, prevX, prevY, size, tipLength) {
|
||||
// Berechne den Winkel der Pfeilrichtung
|
||||
var angle = Math.atan2(endY - prevY, endX - prevX);
|
||||
|
||||
// Die zwei hinteren Punkte der Pfeilspitze:
|
||||
// - tipLength zurueck in Pfeilrichtung
|
||||
// - size/2 seitlich (links und rechts)
|
||||
var baseX = endX - tipLength * Math.cos(angle);
|
||||
var baseY = endY - tipLength * Math.sin(angle);
|
||||
|
||||
// Senkrecht zur Pfeilrichtung (90 Grad = PI/2)
|
||||
var perpAngle = angle + Math.PI / 2;
|
||||
var halfWidth = size / 2;
|
||||
|
||||
return [
|
||||
[endX, endY], // Spitze
|
||||
[baseX + halfWidth * Math.cos(perpAngle), baseY + halfWidth * Math.sin(perpAngle)], // Links
|
||||
[baseX - halfWidth * Math.cos(perpAngle), baseY - halfWidth * Math.sin(perpAngle)] // Rechts
|
||||
];
|
||||
},
|
||||
|
||||
createArrow: function(draw, direction, frameRect, options) {
|
||||
var length = options.length;
|
||||
var angle = options.angle;
|
||||
var bendPos = options.bendPos;
|
||||
var color = options.color;
|
||||
var weight = options.weight;
|
||||
var dashArray = options.dashArray;
|
||||
var arrowSize = options.arrowSize;
|
||||
var tipLength = options.tipLength;
|
||||
|
||||
var angleRad = angle * Math.PI / 180;
|
||||
var bend = bendPos / 100;
|
||||
|
||||
var startX, startY, midX, midY, endX, endY;
|
||||
var fx = frameRect.x;
|
||||
var fy = frameRect.y;
|
||||
var fw = frameRect.width;
|
||||
var fh = frameRect.height;
|
||||
|
||||
// Fuer Standalone-Pfeile (width=0, height=0) ist fx,fy der Startpunkt
|
||||
var isStandalone = (fw === 0 && fh === 0);
|
||||
var cx = isStandalone ? fx : fx + fw / 2;
|
||||
var cy = isStandalone ? fy : fy + fh / 2;
|
||||
|
||||
switch(direction) {
|
||||
case 'top':
|
||||
startX = cx;
|
||||
startY = isStandalone ? fy : fy;
|
||||
midX = startX; midY = startY - length * bend;
|
||||
endX = midX + Math.sin(angleRad) * (length * (1 - bend));
|
||||
endY = midY - Math.cos(angleRad) * (length * (1 - bend));
|
||||
break;
|
||||
case 'bottom':
|
||||
startX = cx;
|
||||
startY = isStandalone ? fy : fy + fh;
|
||||
midX = startX; midY = startY + length * bend;
|
||||
endX = midX + Math.sin(angleRad) * (length * (1 - bend));
|
||||
endY = midY + Math.cos(angleRad) * (length * (1 - bend));
|
||||
break;
|
||||
case 'left':
|
||||
startX = isStandalone ? fx : fx;
|
||||
startY = cy;
|
||||
midX = startX - length * bend; midY = startY;
|
||||
endX = midX - Math.cos(angleRad) * (length * (1 - bend));
|
||||
endY = midY + Math.sin(angleRad) * (length * (1 - bend));
|
||||
break;
|
||||
case 'right':
|
||||
startX = fx + fw; startY = cy;
|
||||
midX = startX + length * bend; midY = startY;
|
||||
endX = midX + Math.cos(angleRad) * (length * (1 - bend));
|
||||
endY = midY + Math.sin(angleRad) * (length * (1 - bend));
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
var points = (angle !== 0 && bend > 0 && bend < 1)
|
||||
? [[startX, startY], [midX, midY], [endX, endY]]
|
||||
: [[startX, startY], [endX, endY]];
|
||||
|
||||
var pathData = '';
|
||||
for (var i = 0; i < points.length; i++) {
|
||||
pathData += (i === 0 ? 'M' : 'L') + points[i][0] + ' ' + points[i][1] + ' ';
|
||||
}
|
||||
|
||||
var line = draw.path(pathData).fill('none').stroke({ color: color, width: weight });
|
||||
if (dashArray) line.attr('stroke-dasharray', dashArray);
|
||||
|
||||
var lastPoint = points[points.length - 1];
|
||||
var prevPoint = points[points.length - 2];
|
||||
var headPoints = this.calculateArrowHead(lastPoint[0], lastPoint[1], prevPoint[0], prevPoint[1], arrowSize, tipLength);
|
||||
var arrowHead = draw.polygon(headPoints).fill(color).stroke({ color: color, width: 1 });
|
||||
|
||||
return draw.group().add(line).add(arrowHead);
|
||||
},
|
||||
|
||||
createText: function(draw, text, x, y, options) {
|
||||
var fontSize = options.fontSize;
|
||||
var color = options.color;
|
||||
var lines = options.lines;
|
||||
var lineHeight = options.lineHeight;
|
||||
var group = draw.group();
|
||||
|
||||
var totalHeight = lines.length * lineHeight;
|
||||
var startY = y - (totalHeight / 2) + (fontSize * 0.8);
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var lineY = startY + (i * lineHeight);
|
||||
var lineText = lines[i] || ' ';
|
||||
// Verwende plain SVG text element fuer bessere Kontrolle
|
||||
var textEl = draw.plain(lineText)
|
||||
.font({
|
||||
family: 'Arial, sans-serif',
|
||||
size: fontSize,
|
||||
anchor: 'middle'
|
||||
})
|
||||
.fill(color)
|
||||
.attr('x', x)
|
||||
.attr('y', lineY)
|
||||
.attr('text-anchor', 'middle');
|
||||
group.add(textEl);
|
||||
}
|
||||
return group;
|
||||
},
|
||||
|
||||
generate: function(state) {
|
||||
var s = state;
|
||||
var textMetrics = this.measureText(s.text || 'Text', s.fontSize);
|
||||
|
||||
var frameScale = s.frameScale / 100;
|
||||
var frameWidth = (textMetrics.width + s.paddingLeft + s.paddingRight) * frameScale;
|
||||
var frameHeight = (textMetrics.height + s.paddingTop + s.paddingBottom) * frameScale;
|
||||
|
||||
var minWidth = Math.max(frameWidth, 40);
|
||||
var minHeight = Math.max(frameHeight, 30);
|
||||
|
||||
// Pfeil-Raum berechnen inkl. Winkel-Abweichung
|
||||
var arrowSpace = 0;
|
||||
var arrowSideSpace = 0;
|
||||
if (s.arrow !== 'none') {
|
||||
var angleRad = Math.abs(s.arrowAngle) * Math.PI / 180;
|
||||
var bendFactor = s.arrowBend / 100;
|
||||
var effectiveLength = s.arrowLength * (1 - bendFactor);
|
||||
|
||||
// Hauptrichtung: volle Laenge + Spitze + Puffer
|
||||
arrowSpace = s.arrowLength + s.arrowTipLength + 20;
|
||||
|
||||
// Seitliche Abweichung durch Winkel
|
||||
arrowSideSpace = Math.abs(Math.sin(angleRad) * effectiveLength) + s.arrowSize + 10;
|
||||
}
|
||||
|
||||
var svgWidth = minWidth;
|
||||
var svgHeight = minHeight;
|
||||
var offsetX = 0, offsetY = 0;
|
||||
|
||||
switch(s.arrow) {
|
||||
case 'left':
|
||||
svgWidth += arrowSpace;
|
||||
svgHeight = Math.max(svgHeight, minHeight + arrowSideSpace * 2);
|
||||
offsetX = arrowSpace;
|
||||
offsetY = (svgHeight - minHeight) / 2;
|
||||
break;
|
||||
case 'right':
|
||||
svgWidth += arrowSpace;
|
||||
svgHeight = Math.max(svgHeight, minHeight + arrowSideSpace * 2);
|
||||
offsetY = (svgHeight - minHeight) / 2;
|
||||
break;
|
||||
case 'top':
|
||||
svgHeight += arrowSpace;
|
||||
svgWidth = Math.max(svgWidth, minWidth + arrowSideSpace * 2);
|
||||
offsetY = arrowSpace;
|
||||
offsetX = (svgWidth - minWidth) / 2;
|
||||
break;
|
||||
case 'bottom':
|
||||
svgHeight += arrowSpace;
|
||||
svgWidth = Math.max(svgWidth, minWidth + arrowSideSpace * 2);
|
||||
offsetX = (svgWidth - minWidth) / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
var draw = SVG().size(svgWidth, svgHeight);
|
||||
|
||||
var frameX = offsetX;
|
||||
var frameY = offsetY;
|
||||
var frameCx = frameX + minWidth / 2;
|
||||
var frameCy = frameY + minHeight / 2;
|
||||
|
||||
// Text-Verschiebung basierend auf asymmetrischem Padding
|
||||
// Wenn links mehr Padding ist, verschiebt sich der Text nach rechts
|
||||
var textOffsetX = (s.paddingLeft - s.paddingRight) / 2;
|
||||
var textOffsetY = (s.paddingTop - s.paddingBottom) / 2;
|
||||
var textCx = frameCx + textOffsetX;
|
||||
var textCy = frameCy + textOffsetY;
|
||||
|
||||
var dashArray = this.getDashArray(s.lineStyle, s.lineWeight);
|
||||
var strokeStyle = { color: s.frameColor, weight: s.lineWeight, dashArray: dashArray };
|
||||
|
||||
if (s.shape !== 'none') {
|
||||
this.createShape(draw, s.shape, frameX, frameY, minWidth, minHeight, strokeStyle);
|
||||
}
|
||||
|
||||
if (s.arrow !== 'none') {
|
||||
this.createArrow(draw, s.arrow, {
|
||||
x: frameX, y: frameY, width: minWidth, height: minHeight
|
||||
}, {
|
||||
length: s.arrowLength, angle: s.arrowAngle, bendPos: s.arrowBend,
|
||||
color: s.frameColor, weight: s.lineWeight, dashArray: dashArray,
|
||||
arrowSize: s.arrowSize, tipLength: s.arrowTipLength
|
||||
});
|
||||
}
|
||||
|
||||
this.createText(draw, s.text, textCx, textCy, {
|
||||
fontSize: s.fontSize, color: s.textColor,
|
||||
lines: textMetrics.lines, lineHeight: textMetrics.lineHeight
|
||||
});
|
||||
|
||||
return draw.svg();
|
||||
},
|
||||
|
||||
generateArrowOnly: function(state) {
|
||||
var s = state;
|
||||
if (s.arrow === 'none') return null;
|
||||
|
||||
var length = s.arrowLength;
|
||||
var tipLength = s.arrowTipLength;
|
||||
var arrowSize = s.arrowSize;
|
||||
var padding = 40;
|
||||
|
||||
// Berechne die tatsaechlichen Endpunkte des Pfeils
|
||||
var angleRad = s.arrowAngle * Math.PI / 180;
|
||||
var bendFactor = s.arrowBend / 100;
|
||||
var straightPart = length * bendFactor;
|
||||
var angledPart = length * (1 - bendFactor);
|
||||
|
||||
// Seitliche Ausdehnung durch den Winkel (mit Vorzeichen)
|
||||
var sideExtent = Math.sin(angleRad) * angledPart;
|
||||
var mainExtent = Math.cos(angleRad) * angledPart;
|
||||
|
||||
// Mindestgroesse fuer die seitliche Ausdehnung
|
||||
var minSideSpace = Math.abs(sideExtent) + arrowSize + tipLength + padding;
|
||||
|
||||
var width, height, startX, startY;
|
||||
|
||||
switch(s.arrow) {
|
||||
case 'right':
|
||||
width = straightPart + mainExtent + tipLength + padding * 2;
|
||||
height = Math.max(arrowSize * 4, minSideSpace * 2);
|
||||
startX = padding;
|
||||
// Bei positivem Winkel geht der Pfeil nach unten, also Start weiter oben
|
||||
startY = height / 2 - sideExtent / 2;
|
||||
break;
|
||||
case 'left':
|
||||
width = straightPart + mainExtent + tipLength + padding * 2;
|
||||
height = Math.max(arrowSize * 4, minSideSpace * 2);
|
||||
startX = width - padding;
|
||||
startY = height / 2 + sideExtent / 2;
|
||||
break;
|
||||
case 'bottom':
|
||||
width = Math.max(arrowSize * 4, minSideSpace * 2);
|
||||
height = straightPart + mainExtent + tipLength + padding * 2;
|
||||
startX = width / 2 - sideExtent / 2;
|
||||
startY = padding;
|
||||
break;
|
||||
case 'top':
|
||||
width = Math.max(arrowSize * 4, minSideSpace * 2);
|
||||
height = straightPart + mainExtent + tipLength + padding * 2;
|
||||
startX = width / 2 + sideExtent / 2;
|
||||
startY = height - padding;
|
||||
break;
|
||||
}
|
||||
|
||||
// Stelle sicher, dass alle Werte positiv sind
|
||||
width = Math.max(width, 80);
|
||||
height = Math.max(height, 60);
|
||||
|
||||
var draw = SVG().size(width, height);
|
||||
|
||||
// Simuliere einen Rahmen-Punkt als Startpunkt
|
||||
var frameRect = {
|
||||
x: startX,
|
||||
y: startY,
|
||||
width: 0,
|
||||
height: 0
|
||||
};
|
||||
|
||||
this.createArrow(draw, s.arrow, frameRect, {
|
||||
length: s.arrowLength, angle: s.arrowAngle, bendPos: s.arrowBend,
|
||||
color: s.frameColor, weight: s.lineWeight,
|
||||
dashArray: this.getDashArray(s.lineStyle, s.lineWeight),
|
||||
arrowSize: s.arrowSize, tipLength: s.arrowTipLength
|
||||
});
|
||||
|
||||
return draw.svg();
|
||||
}
|
||||
};
|
||||
|
||||
window.SvgGenerator = SvgGenerator;
|
||||
Reference in New Issue
Block a user