From 624aa8828eec85eaa86fe883f0ff9b4f9ef4d2e6 Mon Sep 17 00:00:00 2001 From: "Adrian A. Baumann" Date: Sun, 12 Jul 2026 14:44:17 +0200 Subject: [PATCH] Add Paths/Text output mode toggle with descriptions --- k8s/deployment.yaml | 2 +- web/app.py | 87 ++++++++++++++++++++++++++-------------- web/templates/index.html | 68 +++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 31 deletions(-) diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index 595448c..22f21d2 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -15,7 +15,7 @@ spec: spec: containers: - name: m1730-generator - image: git.baumann.gr/adebaumann/m1730-generator:0.6 + image: git.baumann.gr/adebaumann/m1730-generator:0.7 imagePullPolicy: Always ports: - containerPort: 5000 diff --git a/web/app.py b/web/app.py index 0c9e15b..6f1c8c6 100644 --- a/web/app.py +++ b/web/app.py @@ -1,17 +1,28 @@ import base64 import pathlib from flask import Flask, render_template, request, Response +from html import escape from fontTools.ttLib import TTFont from fontTools.pens.svgPathPen import SVGPathPen app = Flask(__name__) -VERSION = "0.6" +VERSION = "0.7" _LOGO_PATH = pathlib.Path(__file__).parent / 'static' / 'logo.png' _LOGO_B64 = base64.b64encode(_LOGO_PATH.read_bytes()).decode() _FONT_PATH = pathlib.Path('/usr/local/share/fonts/г/ГОСТ_тип_А.ttf') +_FONT_B64 = base64.b64encode(_FONT_PATH.read_bytes()).decode() +_FONT_FACE = ( + '' +) + _FONT_OBJ = TTFont(str(_FONT_PATH)) _GLYPH_SET = _FONT_OBJ.getGlyphSet() _CMAP = _FONT_OBJ.getBestCmap() @@ -19,10 +30,10 @@ _HMTX = _FONT_OBJ['hmtx'].metrics _UPM = _FONT_OBJ['head'].unitsPerEm # SVG absolute coordinate constants (1 user unit = 1 mm) -ZERO_X = 60.844130 -MAX_X = 180.251230 -BAR_Y = 107.54670 -SCALE_LEN = MAX_X - ZERO_X # 119.4071 mm +ZERO_X = 60.844130 +MAX_X = 180.251230 +BAR_Y = 107.54670 +SCALE_LEN = MAX_X - ZERO_X # 119.4071 mm BIG_H = 5.0895 SMALL_H = 2.6683 @@ -44,10 +55,8 @@ def _fmt(v: float) -> str: def _text_to_paths(text, x, y, font_size, anchor='start', fill='#1a1a1a', wrap_transform=None): - """Convert a text string to SVG elements using glyph outlines.""" s = font_size / _UPM - # Compute total advance width for centering if anchor == 'middle': total_w = sum( _HMTX.get(_CMAP.get(ord(ch)), (0, 0))[0] * s @@ -75,7 +84,7 @@ def _text_to_paths(text, x, y, font_size, anchor='start', fill='#1a1a1a', wrap_t return inner -def generate_svg(unit, min_val, max_val, range_label, big_ticks, small_ticks, label_count): +def generate_svg(unit, min_val, max_val, range_label, big_ticks, small_ticks, label_count, use_paths=True): big_interval = SCALE_LEN / big_ticks big_xs = [ZERO_X + i * big_interval for i in range(big_ticks + 1)] @@ -87,23 +96,37 @@ def generate_svg(unit, min_val, max_val, range_label, big_ticks, small_ticks, la '', - ' ', + ] - # Background rect with scale-bar cutout + if use_paths: + parts.append(' ') + else: + parts += [_FONT_FACE, ' '] + + parts.append( ' ', + ' M 58.443978,107.54692 H 183.64409 v 6.19963 H 58.443978 Z" />' + ) - # Unit label — paths in local coordinate space, wrapped in the same scale transform - ' ' + _text_to_paths( - unit, 41.455711, 109.03796, _unit_font_size(unit), - wrap_transform=f'scale({_UNIT_SCALE_X},1.0364155)' - ), - - # Range label inside the strip on the right - ' ' + _text_to_paths(range_label, 184.79707, 113.6755, 3.60539), - ] + if use_paths: + parts += [ + ' ' + _text_to_paths( + unit, 41.455711, 109.03796, _unit_font_size(unit), + wrap_transform=f'scale({_UNIT_SCALE_X},1.0364155)' + ), + ' ' + _text_to_paths(range_label, 184.79707, 113.6755, 3.60539), + ] + else: + fs = _unit_font_size(unit) + parts += [ + f' {escape(unit)}', + f' {escape(range_label)}', + ] # Major ticks for x in big_xs: @@ -123,15 +146,18 @@ def generate_svg(unit, min_val, max_val, range_label, big_ticks, small_ticks, la f' style="stroke:#1a1a1a;stroke-width:0.302;stroke-linecap:butt" />' ) - # Numeric labels centered on their tick + # Numeric labels for idx in label_indices: x = big_xs[idx] val = min_val + (max_val - min_val) * idx / big_ticks - parts.append( - ' ' + _text_to_paths( - _fmt(val), x, 101.49598, 8.89194, anchor='middle' + label = _fmt(val) + if use_paths: + parts.append(' ' + _text_to_paths(label, x, 101.49598, 8.89194, anchor='middle')) + else: + parts.append( + f' {escape(label)}' ) - ) parts += [' ', ''] return '\n'.join(parts) @@ -144,15 +170,16 @@ def index(): @app.route('/generate', methods=['POST']) def generate(): - unit = request.form.get('unit', '%') - min_val = float(request.form.get('min', 0)) - max_val = float(request.form.get('max', 100)) + unit = request.form.get('unit', '%') + min_val = float(request.form.get('min', 0)) + max_val = float(request.form.get('max', 100)) range_label = request.form.get('range_label', '5mA') - big_ticks = max(1, min(50, int(request.form.get('big_ticks', 10)))) + big_ticks = max(1, min(50, int(request.form.get('big_ticks', 10)))) small_ticks = max(0, min(20, int(request.form.get('small_ticks', 4)))) label_count = max(2, int(request.form.get('label_count', 6))) + use_paths = request.form.get('output_mode', 'paths') == 'paths' - svg = generate_svg(unit, min_val, max_val, range_label, big_ticks, small_ticks, label_count) + svg = generate_svg(unit, min_val, max_val, range_label, big_ticks, small_ticks, label_count, use_paths) return Response(svg, mimetype='image/svg+xml') diff --git a/web/templates/index.html b/web/templates/index.html index 3115a5e..96187c2 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -131,12 +131,42 @@ .field input:focus { border-color: var(--accent); } + /* ── Mode toggle ── */ + .mode-toggle { + display: inline-flex; + border: 1px solid var(--rule); + border-radius: 1px; + overflow: hidden; + } + + .mode-btn { + font-family: var(--f-mono); + font-size: 0.72rem; + padding: 0.35rem 0.85rem; + border: none; + background: var(--white); + color: var(--muted); + cursor: pointer; + transition: background 0.12s, color 0.12s; + line-height: 1; + } + + .mode-btn + .mode-btn { + border-left: 1px solid var(--rule); + } + + .mode-btn.active { + background: var(--ink); + color: var(--white); + } + /* ── Actions ── */ .actions { display: flex; gap: 0.75rem; align-items: center; padding-top: 1.75rem; + flex-wrap: wrap; } .btn { @@ -220,6 +250,17 @@ @media (prefers-reduced-motion: reduce) { * { transition: none !important; } } + + .version-tag { + position: fixed; + bottom: 0.75rem; + right: 1rem; + font-family: var(--f-mono); + font-size: 0.65rem; + color: var(--rule); + pointer-events: none; + user-select: none; + } @@ -284,6 +325,16 @@
+
+
+ + +
+ Glyphs as outlines — no font required +
+
@@ -295,6 +346,8 @@ + v{{ version }} +