diff --git a/src/textual_webterm/svg_exporter.py b/src/textual_webterm/svg_exporter.py index 1fd9d28..580ea03 100644 --- a/src/textual_webterm/svg_exporter.py +++ b/src/textual_webterm/svg_exporter.py @@ -255,6 +255,17 @@ class _Span(TypedDict): has_bg: bool +def _is_box_drawing(char: str) -> bool: + """Check if character is a box-drawing or block element that needs precise positioning.""" + if not char: + return False + code = ord(char[0]) + # Box Drawing: U+2500-U+257F + # Block Elements: U+2580-U+259F + # Geometric Shapes (some): U+25A0-U+25FF + return 0x2500 <= code <= 0x259F or 0x25A0 <= code <= 0x25FF + + def _build_row_spans( row_data: list[CharData], default_fg: str, @@ -286,9 +297,15 @@ def _build_row_spans( has_bg = bg != default_bg + # Don't merge box-drawing characters - they need precise x positioning + is_box = _is_box_drawing(char_data) + prev_is_box = current_span is not None and _is_box_drawing(current_span["text"][-1:]) + # Check if we can extend current span if ( current_span is not None + and not is_box + and not prev_is_box and current_span["fg"] == fg and current_span["bg"] == bg and current_span["bold"] == char["bold"] diff --git a/tests/test_svg_exporter.py b/tests/test_svg_exporter.py index 311b405..58e04c3 100644 --- a/tests/test_svg_exporter.py +++ b/tests/test_svg_exporter.py @@ -716,14 +716,17 @@ class TestEdgeCases: assert "AδΈ­BπŸŽ‰C" in svg def test_special_unicode_blocks(self) -> None: - """Unicode box drawing characters render.""" + """Unicode box drawing characters render (separately for precise positioning).""" buffer = [[ self._char("β”Œ"), self._char("─"), self._char("┐"), ]] svg = render_terminal_svg(buffer, width=3, height=1) - assert "β”Œβ”€β”" in svg + # Box drawing chars are rendered separately for precise x positioning + assert "β”Œ" in svg + assert "─" in svg + assert "┐" in svg def test_ansi_bright_colors(self) -> None: """All bright ANSI colors render."""