From acfd23ec57baea7cd1fb74754d1650e22b744e6c Mon Sep 17 00:00:00 2001 From: architeur Date: Sun, 14 Dec 2025 21:51:20 +0100 Subject: [PATCH] Fix Gitea Issue #1: Text-Generator Verbesserungen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rahmenform Standard auf 'Rechteck' gesetzt (statt 'Keiner') - Pfeile werden nun korrekt im SVG-Bereich dargestellt (viewBox + Padding) - Numerische Eingabefelder neben allen Slidern hinzugefügt - Text-Positionierung im Rahmen ermöglicht (horizontal + vertikal) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- symbols/css/text-generator.css | 56 +++++++++ symbols/index.html | 56 ++++++--- symbols/js/text-generator/state.js | 4 + symbols/js/text-generator/svg-generator.js | 67 ++++++++--- symbols/js/text-generator/ui-bindings.js | 133 +++++++++++++++++---- 5 files changed, 265 insertions(+), 51 deletions(-) diff --git a/symbols/css/text-generator.css b/symbols/css/text-generator.css index 94d1730..afa9844 100644 --- a/symbols/css/text-generator.css +++ b/symbols/css/text-generator.css @@ -245,6 +245,34 @@ color: var(--gray-600); } +/* ========== ALIGN OPTIONS ========== */ +.align-options { + display: flex; + gap: 0.35rem; +} + +.align-btn { + padding: 0.4rem 0.65rem; + border: 1px solid var(--gray-300); + background: white; + border-radius: 6px; + cursor: pointer; + font-size: 0.8rem; + transition: all 0.2s; + color: var(--gray-600); +} + +.align-btn:hover { + border-color: var(--primary); + color: var(--primary); +} + +.align-btn.active { + background: var(--primary); + color: white; + border-color: var(--primary); +} + /* ========== ARROW OPTIONS ========== */ .arrow-options { display: flex; @@ -320,6 +348,34 @@ input[type="range"]::-moz-range-thumb { border: none; } +/* ========== NUMBER INPUTS ========== */ +.num-input { + width: 55px; + padding: 0.25rem 0.4rem; + border: 1px solid var(--gray-300); + border-radius: 4px; + font-size: 0.8rem; + text-align: center; + transition: border-color 0.2s, box-shadow 0.2s; +} + +.num-input:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1); +} + +.num-input::-webkit-inner-spin-button, +.num-input::-webkit-outer-spin-button { + opacity: 1; +} + +.num-unit { + font-size: 0.75rem; + color: var(--gray-500); + min-width: 20px; +} + /* ========== RESET BUTTON ========== */ .btn-reset { background: #ef4444; diff --git a/symbols/index.html b/symbols/index.html index 01dadc5..85fd692 100644 --- a/symbols/index.html +++ b/symbols/index.html @@ -48,7 +48,8 @@
- 16px + + px
@@ -75,8 +76,8 @@
- - + + @@ -86,20 +87,22 @@
-
+ +
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+
@@ -166,31 +189,36 @@
- 40px + + px
- + + °
- 50% + + %
- + - 10px + + px
- + - 15px + + px
diff --git a/symbols/js/text-generator/state.js b/symbols/js/text-generator/state.js index 2639c0b..7083cd0 100644 --- a/symbols/js/text-generator/state.js +++ b/symbols/js/text-generator/state.js @@ -16,6 +16,8 @@ const TextGenState = { paddingRight: 10, paddingBottom: 10, paddingLeft: 10, + textAlignX: 'center', + textAlignY: 'center', lineStyle: 'solid', lineWeight: 2, arrow: 'none', @@ -38,6 +40,8 @@ const TextGenState = { paddingRight: 10, paddingBottom: 10, paddingLeft: 10, + textAlignX: 'center', + textAlignY: 'center', lineStyle: 'solid', lineWeight: 2, arrow: 'none', diff --git a/symbols/js/text-generator/svg-generator.js b/symbols/js/text-generator/svg-generator.js index 65938fc..cc1c734 100644 --- a/symbols/js/text-generator/svg-generator.js +++ b/symbols/js/text-generator/svg-generator.js @@ -183,25 +183,49 @@ var SvgGenerator = { var color = options.color; var lines = options.lines; var lineHeight = options.lineHeight; + var alignX = options.alignX || 'center'; + var alignY = options.alignY || 'center'; + var frameWidth = options.frameWidth || 0; + var frameHeight = options.frameHeight || 0; var group = draw.group(); var totalHeight = lines.length * lineHeight; - var startY = y - (totalHeight / 2) + (fontSize * 0.8); + var startY; + + // Vertikale Ausrichtung + switch(alignY) { + case 'top': + startY = y - frameHeight / 2 + fontSize * 0.8 + 5; + break; + case 'bottom': + startY = y + frameHeight / 2 - totalHeight + fontSize * 0.8 - 5; + break; + default: // center + startY = y - (totalHeight / 2) + (fontSize * 0.8); + } + + // Text-Anchor fuer horizontale Ausrichtung + var anchor = alignX === 'left' ? 'start' : (alignX === 'right' ? 'end' : 'middle'); + var textX = x; + if (alignX === 'left') { + textX = x - frameWidth / 2 + 5; + } else if (alignX === 'right') { + textX = x + frameWidth / 2 - 5; + } 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' + anchor: anchor }) .fill(color) - .attr('x', x) + .attr('x', textX) .attr('y', lineY) - .attr('text-anchor', 'middle'); + .attr('text-anchor', anchor); group.add(textEl); } return group; @@ -224,13 +248,13 @@ var SvgGenerator = { if (s.arrow !== 'none') { var angleRad = Math.abs(s.arrowAngle) * Math.PI / 180; var bendFactor = s.arrowBend / 100; - var effectiveLength = s.arrowLength * (1 - bendFactor); + var angledPart = s.arrowLength * (1 - bendFactor); - // Hauptrichtung: volle Laenge + Spitze + Puffer - arrowSpace = s.arrowLength + s.arrowTipLength + 20; + // Hauptrichtung: volle Laenge + Spitze + grosszuegiger Puffer + arrowSpace = s.arrowLength + s.arrowTipLength + s.arrowSize + 25; - // Seitliche Abweichung durch Winkel - arrowSideSpace = Math.abs(Math.sin(angleRad) * effectiveLength) + s.arrowSize + 10; + // Seitliche Abweichung durch Winkel (beruecksichtige Pfeilspitze) + arrowSideSpace = Math.abs(Math.sin(angleRad) * angledPart) + s.arrowSize + s.arrowTipLength + 15; } var svgWidth = minWidth; @@ -262,7 +286,14 @@ var SvgGenerator = { break; } - var draw = SVG().size(svgWidth, svgHeight); + // Puffer um alle Elemente + var padding = 5; + svgWidth += padding * 2; + svgHeight += padding * 2; + offsetX += padding; + offsetY += padding; + + var draw = SVG().size(svgWidth, svgHeight).viewbox(0, 0, svgWidth, svgHeight); var frameX = offsetX; var frameY = offsetY; @@ -295,7 +326,11 @@ var SvgGenerator = { this.createText(draw, s.text, textCx, textCy, { fontSize: s.fontSize, color: s.textColor, - lines: textMetrics.lines, lineHeight: textMetrics.lineHeight + lines: textMetrics.lines, lineHeight: textMetrics.lineHeight, + alignX: s.textAlignX || 'center', + alignY: s.textAlignY || 'center', + frameWidth: minWidth, + frameHeight: minHeight }); return draw.svg(); @@ -353,11 +388,11 @@ var SvgGenerator = { break; } - // Stelle sicher, dass alle Werte positiv sind - width = Math.max(width, 80); - height = Math.max(height, 60); + // Stelle sicher, dass alle Werte positiv sind und genuegend Platz haben + width = Math.max(width, 100); + height = Math.max(height, 80); - var draw = SVG().size(width, height); + var draw = SVG().size(width, height).viewbox(0, 0, width, height); // Simuliere einen Rahmen-Punkt als Startpunkt var frameRect = { diff --git a/symbols/js/text-generator/ui-bindings.js b/symbols/js/text-generator/ui-bindings.js index 0e6a16d..d4595a1 100644 --- a/symbols/js/text-generator/ui-bindings.js +++ b/symbols/js/text-generator/ui-bindings.js @@ -20,6 +20,7 @@ var UiBindings = { // Text & Farben e.textInput = document.getElementById('customText'); e.fontSizeInput = document.getElementById('fontSize'); + e.fontSizeNum = document.getElementById('fontSizeNum'); e.fontSizeValue = document.getElementById('fontSizeValue'); e.textColorInput = document.getElementById('textColor'); e.textColorValue = document.getElementById('textColorValue'); @@ -28,11 +29,13 @@ var UiBindings = { // Rahmen e.frameScaleInput = document.getElementById('frameScale'); + e.frameScaleNum = document.getElementById('frameScaleNum'); e.frameScaleValue = document.getElementById('frameScaleValue'); e.frameScaleRow = document.getElementById('frameScaleRow'); // Padding e.paddingAllInput = document.getElementById('paddingAll'); + e.paddingAllNum = document.getElementById('paddingAllNum'); e.paddingAllValue = document.getElementById('paddingAllValue'); e.paddingTopInput = document.getElementById('paddingTop'); e.paddingTopValue = document.getElementById('paddingTopValue'); @@ -47,14 +50,19 @@ var UiBindings = { // Pfeil e.arrowLengthInput = document.getElementById('arrowLength'); + e.arrowLengthNum = document.getElementById('arrowLengthNum'); e.arrowLengthValue = document.getElementById('arrowLengthValue'); e.arrowAngleInput = document.getElementById('arrowAngle'); + e.arrowAngleNum = document.getElementById('arrowAngleNum'); e.arrowAngleValue = document.getElementById('arrowAngleValue'); e.arrowBendInput = document.getElementById('arrowBend'); + e.arrowBendNum = document.getElementById('arrowBendNum'); e.arrowBendValue = document.getElementById('arrowBendValue'); e.arrowSizeInput = document.getElementById('arrowSize'); + e.arrowSizeNum = document.getElementById('arrowSizeNum'); e.arrowSizeValue = document.getElementById('arrowSizeValue'); e.arrowTipLengthInput = document.getElementById('arrowTipLength'); + e.arrowTipLengthNum = document.getElementById('arrowTipLengthNum'); e.arrowTipLengthValue = document.getElementById('arrowTipLengthValue'); e.arrowDetailsRow = document.getElementById('arrowDetailsRow'); e.arrowDetailsRow2 = document.getElementById('arrowDetailsRow2'); @@ -63,6 +71,11 @@ var UiBindings = { e.textPreview = document.getElementById('textPreview'); e.standaloneArrowPreview = document.getElementById('standaloneArrowPreview'); + // Text-Ausrichtung + e.textAlignRow = document.getElementById('textAlignRow'); + e.alignXButtons = document.querySelectorAll('[data-align-x]'); + e.alignYButtons = document.querySelectorAll('[data-align-y]'); + // Buttons e.shapeButtons = document.querySelectorAll('.shape-btn'); e.arrowButtons = document.querySelectorAll('.arrow-btn'); @@ -83,12 +96,23 @@ var UiBindings = { }); } - // Schriftgroesse + // Schriftgroesse - Slider und Number-Input synchronisieren if (e.fontSizeInput) { e.fontSizeInput.addEventListener('input', function(ev) { var val = parseInt(ev.target.value); state.set('fontSize', val); if (e.fontSizeValue) e.fontSizeValue.textContent = val + 'px'; + if (e.fontSizeNum) e.fontSizeNum.value = val; + self.updatePreview(); + }); + } + if (e.fontSizeNum) { + e.fontSizeNum.addEventListener('input', function(ev) { + var val = parseInt(ev.target.value) || 8; + val = Math.max(8, Math.min(48, val)); + state.set('fontSize', val); + if (e.fontSizeInput) e.fontSizeInput.value = val; + if (e.fontSizeValue) e.fontSizeValue.textContent = val + 'px'; self.updatePreview(); }); } @@ -109,37 +133,55 @@ var UiBindings = { }); } - // Rahmen-Skalierung + // Rahmen-Skalierung - Slider und Number-Input synchronisieren if (e.frameScaleInput) { e.frameScaleInput.addEventListener('input', function(ev) { var val = parseInt(ev.target.value); state.set('frameScale', val); if (e.frameScaleValue) e.frameScaleValue.textContent = val + '%'; + if (e.frameScaleNum) e.frameScaleNum.value = val; + self.updatePreview(); + }); + } + if (e.frameScaleNum) { + e.frameScaleNum.addEventListener('input', function(ev) { + var val = parseInt(ev.target.value) || 50; + val = Math.max(50, Math.min(200, val)); + state.set('frameScale', val); + if (e.frameScaleInput) e.frameScaleInput.value = val; + if (e.frameScaleValue) e.frameScaleValue.textContent = val + '%'; self.updatePreview(); }); } - // Gesamt-Padding + // Gesamt-Padding - Slider und Number-Input synchronisieren + var syncAllPadding = function(val) { + state.setMultiple({ + paddingTop: val, paddingRight: val, paddingBottom: val, paddingLeft: val + }); + if (e.paddingAllValue) e.paddingAllValue.textContent = val + 'px'; + if (e.paddingAllNum) e.paddingAllNum.value = val; + if (e.paddingAllInput) e.paddingAllInput.value = val; + if (e.paddingTopInput) e.paddingTopInput.value = val; + if (e.paddingTopValue) e.paddingTopValue.textContent = val + 'px'; + if (e.paddingRightInput) e.paddingRightInput.value = val; + if (e.paddingRightValue) e.paddingRightValue.textContent = val + 'px'; + if (e.paddingBottomInput) e.paddingBottomInput.value = val; + if (e.paddingBottomValue) e.paddingBottomValue.textContent = val + 'px'; + if (e.paddingLeftInput) e.paddingLeftInput.value = val; + if (e.paddingLeftValue) e.paddingLeftValue.textContent = val + 'px'; + self.updatePreview(); + }; if (e.paddingAllInput) { e.paddingAllInput.addEventListener('input', function(ev) { - var val = parseInt(ev.target.value); - state.setMultiple({ - paddingTop: val, - paddingRight: val, - paddingBottom: val, - paddingLeft: val - }); - if (e.paddingAllValue) e.paddingAllValue.textContent = val + 'px'; - // Einzelne Slider synchronisieren - if (e.paddingTopInput) e.paddingTopInput.value = val; - if (e.paddingTopValue) e.paddingTopValue.textContent = val + 'px'; - if (e.paddingRightInput) e.paddingRightInput.value = val; - if (e.paddingRightValue) e.paddingRightValue.textContent = val + 'px'; - if (e.paddingBottomInput) e.paddingBottomInput.value = val; - if (e.paddingBottomValue) e.paddingBottomValue.textContent = val + 'px'; - if (e.paddingLeftInput) e.paddingLeftInput.value = val; - if (e.paddingLeftValue) e.paddingLeftValue.textContent = val + 'px'; - self.updatePreview(); + syncAllPadding(parseInt(ev.target.value)); + }); + } + if (e.paddingAllNum) { + e.paddingAllNum.addEventListener('input', function(ev) { + var val = parseInt(ev.target.value) || 0; + val = Math.max(0, Math.min(50, val)); + syncAllPadding(val); }); } @@ -198,12 +240,34 @@ var UiBindings = { self.updatePreview(); }); }); + + // Align-X-Buttons (horizontal) + e.alignXButtons.forEach(function(btn) { + btn.addEventListener('click', function() { + e.alignXButtons.forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + state.set('textAlignX', btn.dataset.alignX); + self.updatePreview(); + }); + }); + + // Align-Y-Buttons (vertikal) + e.alignYButtons.forEach(function(btn) { + btn.addEventListener('click', function() { + e.alignYButtons.forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + state.set('textAlignY', btn.dataset.alignY); + self.updatePreview(); + }); + }); }, bindSlider: function(inputKey, valueKey, stateKey, unit) { var self = this; var input = this.elements[inputKey]; var valueEl = this.elements[valueKey]; + var numKey = inputKey.replace('Input', 'Num'); + var numInput = this.elements[numKey]; var state = window.TextGenState; if (input) { @@ -211,6 +275,22 @@ var UiBindings = { var val = parseInt(ev.target.value); state.set(stateKey, val); if (valueEl) valueEl.textContent = val + unit; + if (numInput) numInput.value = val; + self.updatePreview(); + if (stateKey.indexOf('arrow') === 0) { + self.updateStandaloneArrowPreview(); + } + }); + } + if (numInput) { + var min = input ? parseInt(input.min) : 0; + var max = input ? parseInt(input.max) : 100; + numInput.addEventListener('input', function(ev) { + var val = parseInt(ev.target.value) || min; + val = Math.max(min, Math.min(max, val)); + state.set(stateKey, val); + if (input) input.value = val; + if (valueEl) valueEl.textContent = val + unit; self.updatePreview(); if (stateKey.indexOf('arrow') === 0) { self.updateStandaloneArrowPreview(); @@ -241,14 +321,17 @@ var UiBindings = { if (e.textInput) e.textInput.value = s.text; if (e.fontSizeInput) e.fontSizeInput.value = s.fontSize; + if (e.fontSizeNum) e.fontSizeNum.value = s.fontSize; if (e.fontSizeValue) e.fontSizeValue.textContent = s.fontSize + 'px'; if (e.textColorInput) e.textColorInput.value = s.textColor; if (e.frameColorInput) e.frameColorInput.value = s.frameColor; if (e.frameScaleInput) e.frameScaleInput.value = s.frameScale; + if (e.frameScaleNum) e.frameScaleNum.value = s.frameScale; if (e.frameScaleValue) e.frameScaleValue.textContent = s.frameScale + '%'; // Padding if (e.paddingAllInput) e.paddingAllInput.value = s.paddingTop; + if (e.paddingAllNum) e.paddingAllNum.value = s.paddingTop; if (e.paddingAllValue) e.paddingAllValue.textContent = s.paddingTop + 'px'; if (e.paddingTopInput) e.paddingTopInput.value = s.paddingTop; if (e.paddingTopValue) e.paddingTopValue.textContent = s.paddingTop + 'px'; @@ -261,14 +344,19 @@ var UiBindings = { // Pfeil if (e.arrowLengthInput) e.arrowLengthInput.value = s.arrowLength; + if (e.arrowLengthNum) e.arrowLengthNum.value = s.arrowLength; if (e.arrowLengthValue) e.arrowLengthValue.textContent = s.arrowLength + 'px'; if (e.arrowAngleInput) e.arrowAngleInput.value = s.arrowAngle; + if (e.arrowAngleNum) e.arrowAngleNum.value = s.arrowAngle; if (e.arrowAngleValue) e.arrowAngleValue.textContent = s.arrowAngle + '\u00B0'; if (e.arrowBendInput) e.arrowBendInput.value = s.arrowBend; + if (e.arrowBendNum) e.arrowBendNum.value = s.arrowBend; if (e.arrowBendValue) e.arrowBendValue.textContent = s.arrowBend + '%'; if (e.arrowSizeInput) e.arrowSizeInput.value = s.arrowSize; + if (e.arrowSizeNum) e.arrowSizeNum.value = s.arrowSize; if (e.arrowSizeValue) e.arrowSizeValue.textContent = s.arrowSize + 'px'; if (e.arrowTipLengthInput) e.arrowTipLengthInput.value = s.arrowTipLength; + if (e.arrowTipLengthNum) e.arrowTipLengthNum.value = s.arrowTipLength; if (e.arrowTipLengthValue) e.arrowTipLengthValue.textContent = s.arrowTipLength + 'px'; // Buttons aktivieren @@ -276,6 +364,8 @@ var UiBindings = { this.activateButton(e.arrowButtons, 'arrow', s.arrow); this.activateButton(e.lineStyleButtons, 'style', s.lineStyle); this.activateButtonByValue(e.lineWeightButtons, 'weight', s.lineWeight); + this.activateButton(e.alignXButtons, 'alignX', s.textAlignX); + this.activateButton(e.alignYButtons, 'alignY', s.textAlignY); }, activateButton: function(buttons, dataAttr, value) { @@ -305,6 +395,7 @@ var UiBindings = { if (e.frameScaleRow) e.frameScaleRow.style.display = hasShape ? 'flex' : 'none'; if (e.paddingAllRow) e.paddingAllRow.style.display = hasShape ? 'flex' : 'none'; if (e.paddingRow) e.paddingRow.style.display = hasShape ? 'flex' : 'none'; + if (e.textAlignRow) e.textAlignRow.style.display = hasShape ? 'flex' : 'none'; if (e.arrowDetailsRow) e.arrowDetailsRow.style.display = hasArrow ? 'flex' : 'none'; if (e.arrowDetailsRow2) e.arrowDetailsRow2.style.display = hasArrow ? 'flex' : 'none'; },