From beda3858e9c3583e0c2dc9dc70e49fad61b5caf2 Mon Sep 17 00:00:00 2001 From: DiTus Date: Sun, 22 Mar 2026 17:10:24 +0100 Subject: [PATCH] fix: Text rendering and settings for horizontal and vertical lines, parallel trendline text --- js/ui/drawing-tools.js | 130 +++++++++++++++++++++++++++-------------- 1 file changed, 86 insertions(+), 44 deletions(-) diff --git a/js/ui/drawing-tools.js b/js/ui/drawing-tools.js index 96caea2..c37fa47 100644 --- a/js/ui/drawing-tools.js +++ b/js/ui/drawing-tools.js @@ -391,7 +391,14 @@ export class DrawingManager { color: defs.color, width: defs.width, style: defs.style, - opacity: defs.opacity + opacity: defs.opacity, + text: defs.text, + textColor: defs.textColor, + fontSize: defs.fontSize, + bold: defs.bold, + italic: defs.italic, + alignVert: defs.alignVert, + alignHorz: defs.alignHorz }); this.setTool(null); } else if (this.activeTool === 'vertical_line') { @@ -402,7 +409,14 @@ export class DrawingManager { color: defs.color, width: defs.width, style: defs.style, - opacity: defs.opacity + opacity: defs.opacity, + text: defs.text, + textColor: defs.textColor, + fontSize: defs.fontSize, + bold: defs.bold, + italic: defs.italic, + alignVert: defs.alignVert, + alignHorz: defs.alignHorz }); this.setTool(null); } else if (this.activeTool === 'fib_retracement') { @@ -783,13 +797,27 @@ export class DrawingManager { const fontStyle = (d.bold ? 'bold ' : '') + (d.italic ? 'italic ' : ''); ctx.font = `${fontStyle}${fontSize}px Inter`; ctx.fillStyle = d.textColor || color; + + let startX = x1, startY = y1, endX = x2, endY = y2; + if (x1 > x2 || (x1 === x2 && y1 > y2)) { + startX = x2; startY = y2; endX = x1; endY = y1; + } + + const angle = Math.atan2(endY - startY, endX - startX); + const length = Math.hypot(endX - startX, endY - startY); + + ctx.translate(startX, startY); + ctx.rotate(angle); + ctx.textAlign = d.alignHorz || 'left'; ctx.textBaseline = d.alignVert === 'middle' ? 'middle' : (d.alignVert === 'bottom' ? 'top' : 'bottom'); - const tx = (x1 + x2) / 2; - const ty = (y1 + y2) / 2; + let tx = 0; + if (d.alignHorz === 'center') tx = length / 2; + else if (d.alignHorz === 'right') tx = length; + const offset = 10; - const finalTy = d.alignVert === 'top' ? ty - offset : (d.alignVert === 'bottom' ? ty + offset : ty); + const finalTy = d.alignVert === 'top' ? -offset : (d.alignVert === 'bottom' ? offset : 0); ctx.fillText(d.text, tx, finalTy); ctx.restore(); @@ -837,41 +865,46 @@ export class DrawingManager { ctx.beginPath(); ctx.arc(0, y, 5, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); } - // Store label position for hit detection (before rendering) - let textX = scope.mediaSize.width / 2; - let textY = y; + // Render Text and store label position let labelPos = null; if (d.text) { + ctx.save(); + ctx.setLineDash([]); const fontSize = d.fontSize || 14; const fontStyle = (d.bold ? 'bold ' : '') + (d.italic ? 'italic ' : ''); const font = `${fontStyle}${fontSize}px Inter`; ctx.font = font; + ctx.fillStyle = d.textColor || d.color; + + const alignHorz = d.alignHorz || 'center'; + ctx.textAlign = alignHorz; + ctx.textBaseline = d.alignVert === 'middle' ? 'middle' : (d.alignVert === 'bottom' ? 'top' : 'bottom'); + + let textX = scope.mediaSize.width / 2; + if (alignHorz === 'left') textX = 10; + else if (alignHorz === 'right') textX = scope.mediaSize.width - 10; + + const offset = 10; + let textY = d.alignVert === 'top' ? y - offset : (d.alignVert === 'bottom' ? y + offset : y); + + ctx.fillText(d.text, textX, textY); + const metrics = ctx.measureText(d.text); const labelWidth = metrics.width + 16; const labelHeight = 24; - const defaultLabelX = scope.mediaSize.width / 2 - labelWidth / 2; - const offset = 10; + + let defaultLabelX = scope.mediaSize.width / 2 - labelWidth / 2; + if (alignHorz === 'left') defaultLabelX = 10; + else if (alignHorz === 'right') defaultLabelX = scope.mediaSize.width - labelWidth - 10; + const defaultLabelY = d.alignVert === 'top' ? y - 40 : (d.alignVert === 'bottom' ? y + 20 : y - 12); + labelPos = { x: defaultLabelX + (d.labelOffset?.x || 0), y: defaultLabelY + (d.labelOffset?.y || 0), width: labelWidth, height: labelHeight }; - textX = scope.mediaSize.width / 2; - textY = d.alignVert === 'top' ? y - offset : (d.alignVert === 'bottom' ? y + offset : y); - } - - // Render Text if present - if (d.text) { - ctx.save(); - ctx.setLineDash([]); - ctx.font = font; - ctx.fillStyle = d.textColor || d.color; - - ctx.textAlign = d.alignHorz || 'center'; - ctx.textBaseline = d.alignVert === 'middle' ? 'middle' : (d.alignVert === 'bottom' ? 'top' : 'bottom'); - ctx.fillText(d.text, textX, textY); ctx.restore(); } @@ -902,39 +935,48 @@ export class DrawingManager { ctx.beginPath(); ctx.arc(x, 0, 5, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); } - // Store label position for hit detection (before rendering) - let textX = x; - let textY = 0; + // Render Text and store label position let labelPos = null; if (d.text) { + ctx.save(); + ctx.setLineDash([]); const fontSize = d.fontSize || 14; const fontStyle = (d.bold ? 'bold ' : '') + (d.italic ? 'italic ' : ''); const font = `${fontStyle}${fontSize}px Inter`; ctx.font = font; + ctx.fillStyle = d.textColor || d.color; + + const alignHorz = d.alignHorz || 'center'; + ctx.textAlign = alignHorz; + ctx.textBaseline = d.alignVert === 'middle' ? 'middle' : (d.alignVert === 'bottom' ? 'bottom' : 'top'); + + let textX = x; + const offset = 10; + + let textY = scope.mediaSize.height / 2; // Middle + if (d.alignVert === 'top') textY = offset; + else if (d.alignVert === 'bottom') textY = scope.mediaSize.height - offset; + + ctx.fillText(d.text, textX, textY); + const metrics = ctx.measureText(d.text); const labelWidth = metrics.width + 16; const labelHeight = 24; - const offset = 10; - const defaultLabelY = d.alignVert === 'top' ? -50 : (d.alignVert === 'bottom' ? 20 : -12); + + let defaultLabelY = scope.mediaSize.height / 2 - labelHeight / 2; // Middle + if (d.alignVert === 'top') defaultLabelY = offset; + else if (d.alignVert === 'bottom') defaultLabelY = scope.mediaSize.height - offset - labelHeight; + + let defaultLabelX = x - labelWidth / 2; + if (alignHorz === 'left') defaultLabelX = x - labelWidth; + else if (alignHorz === 'right') defaultLabelX = x; + labelPos = { - x: x - labelWidth / 2 + (d.labelOffset?.x || 0), + x: defaultLabelX + (d.labelOffset?.x || 0), y: defaultLabelY + (d.labelOffset?.y || 0), width: labelWidth, height: labelHeight }; - textY = d.alignVert === 'top' ? -offset : (d.alignVert === 'bottom' ? offset : 0); - } - - // Render Text if present - if (d.text) { - ctx.save(); - ctx.setLineDash([]); - ctx.font = font; - ctx.fillStyle = d.textColor || d.color; - - ctx.textAlign = d.alignHorz || 'center'; - ctx.textBaseline = d.alignVert === 'middle' ? 'middle' : (d.alignVert === 'bottom' ? 'top' : 'bottom'); - ctx.fillText(d.text, textX, textY); ctx.restore(); } @@ -1374,7 +1416,7 @@ export class DrawingManager { applySettings(key, value) { if (this.selectedDrawing) { if (key === 'bold' || key === 'italic' || key === 'fontSize' || key === 'text' || key === 'textColor' || key === 'alignVert' || key === 'alignHorz') { - const isLineWithText = ['trend_line', 'ray', 'rectangle'].includes(this.selectedDrawing.type); + const isLineWithText = ['trend_line', 'ray', 'rectangle', 'horizontal_line', 'vertical_line'].includes(this.selectedDrawing.type); if (!isLineWithText) return; this.selectedDrawing[key] = value; } else {