diff --git a/rhino/text_editor.py b/rhino/text_editor.py index 5422bf3..9a7aeae 100644 --- a/rhino/text_editor.py +++ b/rhino/text_editor.py @@ -377,36 +377,50 @@ def _runs_to_rtf(runs, default_font, base_size_m=0.20): out.append("\\u{}?".format(v)) return "".join(out) + # Globales Newline-Tracking ueber ALLE Runs hinweg — ein `\n` + # zwischen Runs (= eigener Newline-Run) ergibt EINEN \\line. + # Mehrere aufeinanderfolgende `\n` ergeben entsprechend mehrere + # \\line in Reihe (= Leerzeile). body_parts = [] + pending_newlines = 0 # Newlines die noch emittiert werden muessen + + def _emit_text_segment(run, seg): + if not seg: 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 + 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") + body_parts.append("{} {}".format("".join(codes), _escape_no_par(seg))) + for run in runs: raw = run.get("text") or "" + # Jede \n im Text → \\line; alle \\line werden VOR der naechsten + # Text-Section emittiert. Damit bleiben auch leere Zeilen erhalten. segments = raw.split("\n") for i, seg in enumerate(segments): if i > 0: - 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 - 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") - body_parts.append("{} {}".format("".join(codes), _escape_no_par(seg))) + pending_newlines += 1 + if seg: + # Pending Newlines emittieren bevor Text kommt + for _ in range(pending_newlines): + body_parts.append("\\line ") + pending_newlines = 0 + _emit_text_segment(run, seg) + # Trailing newlines auch noch emittieren + for _ in range(pending_newlines): + body_parts.append("\\line ") # ──────────────────────────────────────────────────────────────── # PASS 2: RTF-Header mit JETZT vollstaendigen Tables + Body