diff --git a/rhino/text_editor.py b/rhino/text_editor.py index a204196..5422bf3 100644 --- a/rhino/text_editor.py +++ b/rhino/text_editor.py @@ -198,15 +198,21 @@ class TextEditorBridge(panel_base.BaseBridge): st.get("underline")) # 3. Text-Wrap im Frame — NACH dem Content damit es nicht - # durch RichText-Set zurueckgesetzt wird - for attr in ("FormatWidth", "TextWidth"): + # durch RichText-Set zurueckgesetzt wird. Beide Setter + # versuchen (verschiedene Rhino-Versions-APIs). + applied_w = None + for attr in ("FormatWidth", "TextWidth", "MaskWidth"): try: - setattr(te, attr, width); break + setattr(te, attr, width) + applied_w = attr + break except Exception: pass try: te.TextIsWrapped = True except Exception: try: te.TextWrap = True except Exception: pass + print("[TEXT-EDITOR] wrap: width={} applied_attr={}".format( + width, applied_w)) # 4. Frame um den Text + Mask-Margin frame_kind = (st.get("frame") or "none").lower() @@ -339,9 +345,11 @@ def _runs_to_rtf(runs, default_font, base_size_m=0.20): nontrivial = True; break if not nontrivial: return None - # Font-Tabelle + Color-Tabelle + # ──────────────────────────────────────────────────────────────── + # PASS 1: Runs verarbeiten + Fonts/Colors sammeln + RTF-Bodies bauen + # ──────────────────────────────────────────────────────────────── fonts = [default_font] - colors = [] # nur explizit gesetzte (Index 1+) + colors = [] def font_idx(f): if not f: return 0 if f not in fonts: fonts.append(f) @@ -353,19 +361,6 @@ def _runs_to_rtf(runs, default_font, base_size_m=0.20): colors.append(rgb) return len(colors) - parts = ["{\\rtf1\\ansi\\ansicpg1252\\deff0"] - parts.append("{\\fonttbl") - for i, f in enumerate(fonts): - parts.append("{{\\f{}\\fnil\\fcharset0 {};}}".format(i, f)) - parts.append("}") - if colors: - parts.append("{\\colortbl;") - for r, g, b in colors: - parts.append("\\red{}\\green{}\\blue{};".format(r, g, b)) - parts.append("}") - parts.append("\\pard") - - # Frontend rendert 1m = 100px; Standard-Run-Size ist base_size_m * 100 BASE_PX = max(1.0, base_size_m * 100.0) def _escape_no_par(s): @@ -382,16 +377,19 @@ def _runs_to_rtf(runs, default_font, base_size_m=0.20): out.append("\\u{}?".format(v)) return "".join(out) - # 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. + body_parts = [] 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\n") - if not seg: continue + body_parts.append("\\par\n") + if not seg: + # Leere Section (aufeinanderfolgende \\n) → leerer + # Paragraph mit einem Space, damit Rhinos Parser eine + # echte Leerzeile rendert + body_parts.append(" ") + 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 @@ -408,8 +406,23 @@ def _runs_to_rtf(runs, default_font, base_size_m=0.20): 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))) + body_parts.append("{} {}".format("".join(codes), _escape_no_par(seg))) + # ──────────────────────────────────────────────────────────────── + # PASS 2: RTF-Header mit JETZT vollstaendigen Tables + Body + # ──────────────────────────────────────────────────────────────── + parts = ["{\\rtf1\\ansi\\ansicpg1252\\deff0"] + parts.append("{\\fonttbl") + for i, f in enumerate(fonts): + parts.append("{{\\f{}\\fnil\\fcharset0 {};}}".format(i, f)) + parts.append("}") + if colors: + parts.append("{\\colortbl;") + for r, g, b in colors: + parts.append("\\red{}\\green{}\\blue{};".format(r, g, b)) + parts.append("}") + parts.append("\\pard") + parts.extend(body_parts) parts.append("}") return "".join(parts)