RTF: inline state codes + SetRichText method + Diagnose-Print

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 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 02:19:04 +02:00
parent 211078d229
commit 2d48a6ed3a
+62 -45
View File
@@ -149,31 +149,53 @@ class TextEditorBridge(panel_base.BaseBridge):
except Exception: pass except Exception: pass
te.Plane = plane te.Plane = plane
# 1. Defaults (Font, Height, Align) — gilt fuer ALLES # RichText (Phase 2) erzeugen wenn Runs nontrivial sind.
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.
rtf = _runs_to_rtf( rtf = _runs_to_rtf(
runs, runs,
st.get("font") or "Helvetica", st.get("font") or "Helvetica",
base_size_m=float(st.get("size") or 0.2)) if runs else None base_size_m=float(st.get("size") or 0.2)) if runs else None
if rtf: 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: try:
te.RichText = rtf ds = doc.DimStyles.Current
except Exception as ex: te.SetRichText(rtf, ds)
print("[TEXT-EDITOR] RichText set fail:", ex) applied = True
te.PlainText = text 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: else:
te.PlainText = text 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 # 3. Text-Wrap im Frame — NACH dem Content damit es nicht
# durch RichText-Set zurueckgesetzt wird # 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) BASE_PX = max(1.0, base_size_m * 100.0)
def _escape_no_par(s): def _escape_no_par(s):
"""Wie _rtf_escape aber OHNE \\par-Umwandlung von \\n
(Newlines werden separat als \\par ZWISCHEN Run-Groups emitted)."""
out = [] out = []
for ch in s: for ch in s:
cp = ord(ch) cp = ord(ch)
if ch == "\\": out.append("\\\\") if ch == "\\": out.append("\\\\")
elif ch == "{": out.append("\\{") elif 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) elif cp < 128: out.append(ch)
else: else:
v = cp if cp < 0x8000 else cp - 0x10000 v = cp if cp < 0x8000 else cp - 0x10000
out.append("\\u{}?".format(v)) out.append("\\u{}?".format(v))
return "".join(out) return "".join(out)
def _emit_run_group(run, segment): # State-Tracking — alle Codes werden pro Run IMMER emittiert
"""Emittiert {codes segment} wenn segment nicht leer.""" # (inkl. Reset-Codes wie \\b0 \\i0). Kein {}-Grouping. Klassisches
if not segment: return # Inline-RTF wie Word/Wordpad es ausgibt.
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.
for run in runs: for run in runs:
raw = run.get("text") or "" raw = run.get("text") or ""
segments = raw.split("\n") segments = raw.split("\n")
for i, seg in enumerate(segments): for i, seg in enumerate(segments):
if i > 0: if i > 0:
parts.append("\\par ") parts.append("\\par\n")
esc = _escape_no_par(seg) if not seg: continue
if esc: codes = []
_emit_run_group(run, esc) 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("}") parts.append("}")
return "".join(parts) return "".join(parts)