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:
+62
-45
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user