From 08dda6ab22fcf93ade1913c839abe6c38895cef4 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Sun, 12 Jul 2026 09:07:45 +0300 Subject: [PATCH] Bonsai: keep newlines and multiple spaces in SVG schedules (#5123) The schedule SVG builder extracted cell text with `str(p)` on each odfpy paragraph. `str()` concatenates only raw character data and silently drops ODF whitespace/line-break child elements (`` which encodes runs of two or more spaces, ``, ``), so multi-space runs and in-paragraph newlines disappeared from the rendered schedule. Use `odf.teletype.extractText`, which expands those elements into real characters, and split the result on newlines so each in-paragraph line break becomes its own SVG line. Also set `xml:space="preserve"` on the SVG root, otherwise renderers collapse the now-preserved runs of spaces back to one. Verified live in headless Blender with the real Scheduler().schedule(): a cell "CODE<3 spaces>INFO" now renders "CODE INFO" (was "CODEINFO"), and "FIRSTSECOND" renders as two lines (was "FIRSTSECOND"). Generated with the assistance of an AI coding tool. Co-Authored-By: Claude Opus 4.8 --- src/bonsai/bonsai/bim/module/drawing/scheduler.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/bonsai/bonsai/bim/module/drawing/scheduler.py b/src/bonsai/bonsai/bim/module/drawing/scheduler.py index a1610020c4..d7cd5bf3e5 100644 --- a/src/bonsai/bonsai/bim/module/drawing/scheduler.py +++ b/src/bonsai/bonsai/bim/module/drawing/scheduler.py @@ -30,6 +30,7 @@ from odf.opendocument import load as load_ods from odf.style import Style from odf.table import Table, TableCell, TableColumn, TableRow from odf.text import P +from odf.teletype import extractText import bonsai.tool as tool from bonsai.bim.module.drawing.svgwriter import SvgWriter @@ -63,6 +64,9 @@ class Scheduler: debug=False, id="root", ) + # Preserve runs of whitespace inside cells; without this SVG renderers + # collapse multiple spaces (e.g. from ODF ``) down to one. + self.svg.attribs["xml:space"] = "preserve" self.padding = 1 self.margin = 1 @@ -548,7 +552,14 @@ class Scheduler: :param wrap_text: if True, text will be wrapped to fit in cell :param cell_width: width of cell, used for wrapping text """ - text_lines = [str(p) for p in p_tags] + # `str(p)` only concatenates raw character data and silently drops + # ODF whitespace/line-break elements (``, ``, + # ``), so runs of spaces and in-paragraph newlines were + # lost. `extractText` expands those into real characters; splitting on + # the resulting newlines turns each line break into its own SVG line. + text_lines = [] + for p in p_tags: + text_lines.extend(extractText(p).split("\n")) box_alignment_params = SvgWriter.get_box_alignment_parameters(box_alignment) text_params = {"font-size": font_size} if bold: