From 1596bbd94108b8135fe33c3eae6e1678a89487fc Mon Sep 17 00:00:00 2001 From: karim Date: Thu, 21 May 2026 02:00:03 +0200 Subject: [PATCH] Text-Editor: B/I/U sync via queryCommandState + RTF-Reihenfolge fixen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User: B/I/U Toggle-Buttons hinken hinterher oder zeigen invertiert. Im WYSIWYG sieht alles richtig aus aber Rhino zeigt nur die LETZTE gesetzte Schriftart fuer alles. Fix 1 — B/I/U sync: - selectionchange-Listener pollt jetzt queryCommandState fuer bold/italic/underline und syncen das an Toolbar-State - toggleBold/Italic/Underline machen nur noch exec() — kein manuelles setBold(b => !b) mehr (war out-of-sync wenn execCommand wegen fehlender Selection nicht griff) - B/I/U-Button-Highlight reflektiert jetzt die echte Cursor-Position Fix 2 — RTF nimmt nur letzte Schrift: Reihenfolge im _commit war falsch. Vorher: RichText → TextHeight → _apply_font → _apply_align. _apply_font setzt te.Font (Default-Font) NACH dem RichText → schiesst die per-Run-Fonts in der RTF tot. Neue Reihenfolge: 1. PlainText (Initial-Content) 2. TextHeight, Font, Align (Defaults fuer ALLES) 3. RichText (ueberschreibt Defaults pro Run) Plus Font-Tabelle in _runs_to_rtf jetzt mit \fnil\fcharset0 Marker (Rhinos RTF-Parser kann sonst font-Eintraege ignorieren und Default verwenden). Co-Authored-By: Claude Opus 4.7 --- rhino/text_editor.py | 41 ++++++++++++++++++++++++----------------- src/TextEditorApp.jsx | 12 +++++++++--- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/rhino/text_editor.py b/rhino/text_editor.py index e1a8e79..0a4254d 100644 --- a/rhino/text_editor.py +++ b/rhino/text_editor.py @@ -147,21 +147,13 @@ class TextEditorBridge(panel_base.BaseBridge): except Exception: pass te.Plane = plane - # Rich-Text (Phase 2) wenn vorhanden + nicht-trivial, sonst Plain - 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: - te.RichText = rtf - applied_rtf = True - except Exception as ex: - print("[TEXT-EDITOR] RichText set fail:", ex) - if not applied_rtf: - te.PlainText = text - + # REIHENFOLGE WICHTIG: + # 1. Plain-Text setzen (Initial-Content) + # 2. Default-Font / TextHeight / Align setzen (gilt fuer alles) + # 3. RichText setzen (ZULETZT — die per-Run-Format-Codes + # ueberschreiben dann die Default-Properties) + # Andere Reihenfolge → Defaults schiessen die RTF-Runs weg. + te.PlainText = text try: te.TextHeight = float(st.get("size") or 0.2) except Exception: pass text_create._apply_font( @@ -170,6 +162,19 @@ class TextEditorBridge(panel_base.BaseBridge): st.get("bold"), st.get("italic"), st.get("underline")) text_create._apply_align(te, st.get("align") or "left") + + # RichText (Phase 2) — ZULETZT, ueberschreibt te.Font fuer + # alle Runs die eine eigene Schrift haben. + rtf = _runs_to_rtf( + runs, + st.get("font") or "Helvetica", + base_size_m=float(st.get("size") or 0.2)) if runs else None + if rtf: + try: + te.RichText = rtf + except Exception as ex: + print("[TEXT-EDITOR] RichText set fail:", ex) + for attr in ("FormatWidth", "TextWidth", "MaskWidth"): try: setattr(te, attr, width); break @@ -315,10 +320,12 @@ def _runs_to_rtf(runs, default_font, base_size_m=0.20): colors.append(rgb) return len(colors) - parts = ["{\\rtf1\\ansi\\deff0"] + parts = ["{\\rtf1\\ansi\\ansicpg1252\\deff0"] parts.append("{\\fonttbl") for i, f in enumerate(fonts): - parts.append("{{\\f{} {};}}".format(i, f)) + # Rhinos RTF-Parser will fnil/fcharset0 — sonst kann es passieren + # dass der font-Eintrag ignoriert wird und ein Default verwendet + parts.append("{{\\f{}\\fnil\\fcharset0 {};}}".format(i, f)) parts.append("}") if colors: parts.append("{\\colortbl;") diff --git a/src/TextEditorApp.jsx b/src/TextEditorApp.jsx index 052b49e..cf94e53 100644 --- a/src/TextEditorApp.jsx +++ b/src/TextEditorApp.jsx @@ -237,6 +237,10 @@ export default function TextEditorApp() { if (ed.contains(sel.anchorNode)) { try { savedRangeRef.current = sel.getRangeAt(0).cloneRange() } catch (e) {} + // B/I/U Toolbar-State an die echte Cursor-Position syncen + try { setBold(document.queryCommandState('bold')) } catch (e) {} + try { setItalic(document.queryCommandState('italic')) } catch (e) {} + try { setUnderline(document.queryCommandState('underline')) } catch (e) {} } } document.addEventListener('selectionchange', onSelChange) @@ -322,9 +326,11 @@ export default function TextEditorApp() { fn() } - const toggleBold = () => { setBold(b => !b); exec('bold') } - const toggleItalic = () => { setItalic(b => !b); exec('italic') } - const toggleUnderline = () => { setUnderline(b => !b); exec('underline') } + // setState NICHT manuell — der selectionchange-Listener syncen das + // an die echte queryCommandState-Antwort, sonst hinkt's hinterher. + const toggleBold = () => exec('bold') + const toggleItalic = () => exec('italic') + const toggleUnderline = () => exec('underline') const doAlign = (a) => { setAlign(a) exec(a === 'center' ? 'justifyCenter' : a === 'right' ? 'justifyRight' : 'justifyLeft') } const doSup = () => exec('superscript')