From 2d48a6ed3a7fbb4dad0af580a833802a950e8fe0 Mon Sep 17 00:00:00 2001 From: karim Date: Thu, 21 May 2026 02:19:04 +0200 Subject: [PATCH] RTF: inline state codes + SetRichText method + Diagnose-Print MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User: Mixed-Fonts gehen immer noch nicht — Rhino zeigt nur die letzte gesetzte Schrift, Bold-State auch verloren. _runs_to_rtf neu: klassisches Inline-RTF ohne {} Groups. Pro Run werden ALLE Codes explizit gesetzt (auch Reset-Codes \\b0 \\i0 \\ulnone \\cf0 \\nosupersub) und alle in EINER Zeile mit dem Text: \\pard \\f0\\cf0\\fs60\\b\\i0\\ulnone\\nosupersub Georgia bold \\par \\f1\\cf0\\fs20\\b0\\i0\\ulnone\\nosupersub Helvetica regular Damit ist klar: jeder Run hat eigene state-Definitions. RTF-Parser nimmt nicht "letzte Code wirkt auf alles". _commit-Reihenfolge bei RichText geaendert: KEIN _apply_font wenn RTF verwendet wird. te.Font wuerde sonst die per-Run \\fN Codes ueberschreiben. Method-Switch: te.SetRichText(rtf, dimstyle) zuerst probiert (robuster API), te.RichText property als Fallback, _apply_font als letzter Notfall. Diagnostic: RTF wird jetzt mit Length + 300-char-preview gedruckt ("[TEXT-EDITOR] RTF len=... preview=..."). Bitte Log copy-pasten bei weiteren Issues damit ich seh was tatsaechlich Rhino erreicht. Co-Authored-By: Claude Opus 4.7 --- rhino/text_editor.py | 107 +++++++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 45 deletions(-) diff --git a/rhino/text_editor.py b/rhino/text_editor.py index 81f23ce..a204196 100644 --- a/rhino/text_editor.py +++ b/rhino/text_editor.py @@ -149,31 +149,53 @@ class TextEditorBridge(panel_base.BaseBridge): except Exception: pass te.Plane = plane - # 1. Defaults (Font, Height, Align) — gilt fuer ALLES - try: te.TextHeight = float(st.get("size") or 0.2) - except Exception: pass - text_create._apply_font( - te, - st.get("font") or "Helvetica", - st.get("bold"), st.get("italic"), - st.get("underline")) - text_create._apply_align(te, st.get("align") or "left") - - # 2. Content — RichText wenn vorhanden, sonst PlainText. - # RichText-Runs ueberschreiben Defaults fuer Texte mit - # eigener Formatierung. + # RichText (Phase 2) erzeugen wenn Runs nontrivial sind. 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: + print("[TEXT-EDITOR] RTF len={} preview={!r}".format( + len(rtf), rtf[:300])) + + # Defaults (Height + Align gelten immer) + try: te.TextHeight = float(st.get("size") or 0.2) + except Exception: pass + text_create._apply_align(te, st.get("align") or "left") + + # Content. Bei RichText KEIN _apply_font — sonst ueberschreibt + # te.Font die per-Run-Fonts aus der RTF. Stattdessen lassen + # wir RichText/SetRichText das selber regeln. + if rtf: + te.PlainText = text # Initial-Content (RichText ueberschreibt) + # Bevorzugt SetRichText(rtf, dimstyle) — robusteres API + applied = False try: - te.RichText = rtf - except Exception as ex: - print("[TEXT-EDITOR] RichText set fail:", ex) - te.PlainText = text + ds = doc.DimStyles.Current + te.SetRichText(rtf, ds) + applied = True + print("[TEXT-EDITOR] SetRichText OK") + except Exception as ex1: + print("[TEXT-EDITOR] SetRichText fail:", ex1) + if not applied: + try: + te.RichText = rtf + applied = True + print("[TEXT-EDITOR] te.RichText = OK") + except Exception as ex2: + print("[TEXT-EDITOR] te.RichText = fail:", ex2) + if not applied: + # Letzter Fallback: ohne RTF, mit Toolbar-Defaults + text_create._apply_font( + te, st.get("font") or "Helvetica", + st.get("bold"), st.get("italic"), + st.get("underline")) else: te.PlainText = text + text_create._apply_font( + te, st.get("font") or "Helvetica", + st.get("bold"), st.get("italic"), + st.get("underline")) # 3. Text-Wrap im Frame — NACH dem Content damit es nicht # durch RichText-Set zurueckgesetzt wird @@ -347,51 +369,46 @@ def _runs_to_rtf(runs, default_font, base_size_m=0.20): BASE_PX = max(1.0, base_size_m * 100.0) def _escape_no_par(s): - """Wie _rtf_escape aber OHNE \\par-Umwandlung von \\n - (Newlines werden separat als \\par ZWISCHEN Run-Groups emitted).""" out = [] for ch in s: cp = ord(ch) if ch == "\\": out.append("\\\\") elif ch == "{": out.append("\\{") elif ch == "}": out.append("\\}") - elif ch == "\n": out.append("\n") # passthrough, wird gesplittet + elif ch == "\n": out.append("\n") elif cp < 128: out.append(ch) else: v = cp if cp < 0x8000 else cp - 0x10000 out.append("\\u{}?".format(v)) return "".join(out) - def _emit_run_group(run, segment): - """Emittiert {codes segment} wenn segment nicht leer.""" - if not segment: return - codes = [] - codes.append("\\f{}".format(font_idx(run.get("font") or default_font))) - ci = color_idx(run.get("color")) if run.get("color") else 0 - if ci > 0: codes.append("\\cf{}".format(ci)) - fsp = run.get("fontSizePx") - if fsp and abs(fsp - BASE_PX) > 0.1: - rtf_fs = max(2, int(round(20.0 * fsp / BASE_PX))) - codes.append("\\fs{}".format(rtf_fs)) - # Nur AKTIVE Format-Toggles innerhalb der Group — Default ausserhalb - if run.get("bold"): codes.append("\\b") - if run.get("italic"): codes.append("\\i") - if run.get("underline"): codes.append("\\ul") - if run.get("sup"): codes.append("\\super") - elif run.get("sub"): codes.append("\\sub") - parts.append("{{{} {}}}".format("".join(codes), segment)) - - # Pro Run: nach Newlines splitten. Jedes Segment ist eine eigene - # Group, dazwischen \par fuer den Zeilenumbruch. + # State-Tracking — alle Codes werden pro Run IMMER emittiert + # (inkl. Reset-Codes wie \\b0 \\i0). Kein {}-Grouping. Klassisches + # Inline-RTF wie Word/Wordpad es ausgibt. for run in runs: raw = run.get("text") or "" segments = raw.split("\n") for i, seg in enumerate(segments): if i > 0: - parts.append("\\par ") - esc = _escape_no_par(seg) - if esc: - _emit_run_group(run, esc) + parts.append("\\par\n") + if not seg: continue + codes = [] + codes.append("\\f{}".format(font_idx(run.get("font") or default_font))) + ci = color_idx(run.get("color")) if run.get("color") else 0 + codes.append("\\cf{}".format(ci) if ci > 0 else "\\cf0") + fsp = run.get("fontSizePx") + if fsp and abs(fsp - BASE_PX) > 0.1: + rtf_fs = max(2, int(round(20.0 * fsp / BASE_PX))) + codes.append("\\fs{}".format(rtf_fs)) + else: + codes.append("\\fs20") + codes.append("\\b" if run.get("bold") else "\\b0") + codes.append("\\i" if run.get("italic") else "\\i0") + codes.append("\\ul" if run.get("underline") else "\\ulnone") + if run.get("sup"): codes.append("\\super") + elif run.get("sub"): codes.append("\\sub") + else: codes.append("\\nosupersub") + parts.append("{} {}".format("".join(codes), _escape_no_par(seg))) parts.append("}") return "".join(parts)