From 51987dcc38b100fb18e4530edd728b5f730f3692 Mon Sep 17 00:00:00 2001 From: karim Date: Thu, 21 May 2026 01:54:25 +0200 Subject: [PATCH] Text-Editor: Selection-Preservation + per-Span Font/Size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User-Bug: Stile aendern nichts im Editor oder springen alle in eine Zeile. Mit "Herumfummeln" partiell ge-fixed. Root-Causes: 1. Toolbar-Buttons stehlen Focus aus Editor → Selection futsch → execCommand wirkt auf nichts. Fix: onMouseDown + preventDefault auf B/I/U/Sup/Sub/Align (Pill akzeptiert jetzt onMouseDown prop). 2. Editor-div hat fontFamily/fontSize aus React-State → ueberschreibt per-Span-Styles → alles sieht gleich aus. Fix: editor-div hat statische Defaults (Helvetica 20px), per-Selection Styles wirken ueber span-Wrapping (applyInlineStyleToSelection). 3. Newlines kollabieren (text springt auf eine Zeile). Fix: white-space: pre-wrap auf editor-div. 4. Font/Size dropdowns: alter execCommand fontName war buggy. Neu: applyInlineStyleToSelection('font-family', font) bzw. 'font-size' wickelt die Selektion in ein ein, neue Selection liegt auf dem Span (Folge-Operationen wirken sauber). 5. Selection-change Event-Listener speichert die letzte Editor-Selection in savedRangeRef. restoreSelection() vor jeder Operation stellt sie wieder her — robust auch wenn der Focus zwischendurch weg war. Backend (_runs_to_rtf): BASE_PX = base_size_m * 100 statt hardcoded 14. Frontend rendert 1m = 100px, also entspricht base_size_m*100px dem \\fs20 in RTF (= 1.0× TextEntity.TextHeight). _commit passes settings. size mit, damit das Mapping stimmt. Co-Authored-By: Claude Opus 4.7 --- rhino/text_editor.py | 15 ++++-- src/TextEditorApp.jsx | 119 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 112 insertions(+), 22 deletions(-) diff --git a/rhino/text_editor.py b/rhino/text_editor.py index 9a9bb17..e1a8e79 100644 --- a/rhino/text_editor.py +++ b/rhino/text_editor.py @@ -148,7 +148,10 @@ class TextEditorBridge(panel_base.BaseBridge): te.Plane = plane # Rich-Text (Phase 2) wenn vorhanden + nicht-trivial, sonst Plain - rtf = _runs_to_rtf(runs, st.get("font") or "Helvetica") if runs else None + rtf = _runs_to_rtf( + runs, + st.get("font") or "Helvetica", + base_size_m=float(st.get("size") or 0.2)) if runs else None applied_rtf = False if rtf: try: @@ -282,9 +285,11 @@ def _rtf_escape(s): return "".join(out) -def _runs_to_rtf(runs, default_font): +def _runs_to_rtf(runs, default_font, base_size_m=0.20): """Konvertiert Format-Runs in Rhinos RTF-Dialekt. Runs ist Liste von - dicts mit Keys text/font/bold/italic/underline/sup/sub/color.""" + dicts mit Keys text/font/bold/italic/underline/sup/sub/color/fontSizePx. + base_size_m: TextEntity.TextHeight (in m). Frontend rendert 1m = 100px, + also entspricht base_size_m * 100 dem "Standard" \\fs20 in RTF.""" if not runs: return None # Triviale Runs (alle plain, ein Font) → kein RTF noetig nontrivial = False @@ -322,8 +327,8 @@ def _runs_to_rtf(runs, default_font): parts.append("}") parts.append("\\pard") - # Editor Default-Font-Size in px (siehe TextEditorApp Editor-div: 14px) - BASE_PX = 14 + # Frontend rendert 1m = 100px; Standard-Run-Size ist base_size_m * 100 + BASE_PX = max(1.0, base_size_m * 100.0) for run in runs: codes = [] codes.append("\\f{}".format(font_idx(run.get("font") or default_font))) diff --git a/src/TextEditorApp.jsx b/src/TextEditorApp.jsx index e0836b3..052b49e 100644 --- a/src/TextEditorApp.jsx +++ b/src/TextEditorApp.jsx @@ -20,10 +20,11 @@ const SIZE_PRESETS = [0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.50, 0.70, 1.00] const BAR_H = 22 -function Pill({ children, onClick, active, disabled, title, style }) { +function Pill({ children, onClick, onMouseDown, active, disabled, title, style }) { return (