diff --git a/pyproject.toml b/pyproject.toml index 3cbde15..72e618c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "textual-webterm" -version = "0.3.8" +version = "0.3.9" description = "Serve terminal sessions over the web" authors = ["Will McGugan "] license = "MIT" diff --git a/src/textual_webterm/svg_exporter.py b/src/textual_webterm/svg_exporter.py index 9f88c2a..d467284 100644 --- a/src/textual_webterm/svg_exporter.py +++ b/src/textual_webterm/svg_exporter.py @@ -224,7 +224,8 @@ def render_terminal_svg( # For horizontal box-drawing spans, use textLength to ensure correct width # This prevents gaps caused by font rendering of ─ being narrower than char_width - if _is_all_horizontal_box_drawing(text) and len(text) > 1: + # Use "mostly" check to handle occasional corrupted chars (like U+FFFD) + if _is_mostly_horizontal_box_drawing(text) and len(text) > 1: span_width = columns * char_width attrs.append(f'textLength="{span_width:.1f}"') attrs.append('lengthAdjust="spacing"') @@ -291,11 +292,17 @@ _HORIZONTAL_BOX_CHARS = { } -def _is_all_horizontal_box_drawing(text: str) -> bool: - """Check if text consists entirely of horizontal box-drawing characters.""" +def _is_mostly_horizontal_box_drawing(text: str, threshold: float = 0.8) -> bool: + """Check if text is mostly horizontal box-drawing characters. + + Returns True if at least threshold (default 80%) of chars are horizontal + box-drawing chars. This handles cases where terminal data has occasional + corrupted chars (like replacement char U+FFFD) mixed in. + """ if not text: return False - return all(ord(c) in _HORIZONTAL_BOX_CHARS for c in text) + horizontal_count = sum(1 for c in text if ord(c) in _HORIZONTAL_BOX_CHARS) + return horizontal_count / len(text) >= threshold def _should_break_span(current_text: str, new_char: str) -> bool: diff --git a/tests/test_svg_exporter.py b/tests/test_svg_exporter.py index 6e495ef..96e472c 100644 --- a/tests/test_svg_exporter.py +++ b/tests/test_svg_exporter.py @@ -10,8 +10,8 @@ from textual_webterm.svg_exporter import ( _build_row_spans, _color_to_hex, _escape_xml, - _is_all_horizontal_box_drawing, _is_box_drawing_vertical_or_corner, + _is_mostly_horizontal_box_drawing, _should_break_span, render_terminal_svg, ) @@ -389,26 +389,33 @@ class TestBoxDrawingHelpers: assert _should_break_span("─", "─") is False assert _should_break_span("━", "━") is False - def test_is_all_horizontal_box_drawing_empty(self) -> None: + def test_is_mostly_horizontal_box_drawing_empty(self) -> None: """Empty string returns False.""" - assert _is_all_horizontal_box_drawing("") is False + assert _is_mostly_horizontal_box_drawing("") is False - def test_is_all_horizontal_box_drawing_normal_text(self) -> None: + def test_is_mostly_horizontal_box_drawing_normal_text(self) -> None: """Normal text returns False.""" - assert _is_all_horizontal_box_drawing("Hello") is False - assert _is_all_horizontal_box_drawing("ABC") is False + assert _is_mostly_horizontal_box_drawing("Hello") is False + assert _is_mostly_horizontal_box_drawing("ABC") is False - def test_is_all_horizontal_box_drawing_horizontal_lines(self) -> None: + def test_is_mostly_horizontal_box_drawing_horizontal_lines(self) -> None: """Horizontal box chars return True.""" - assert _is_all_horizontal_box_drawing("─") is True - assert _is_all_horizontal_box_drawing("───") is True - assert _is_all_horizontal_box_drawing("━━━") is True - assert _is_all_horizontal_box_drawing("═══") is True + assert _is_mostly_horizontal_box_drawing("─") is True + assert _is_mostly_horizontal_box_drawing("───") is True + assert _is_mostly_horizontal_box_drawing("━━━") is True + assert _is_mostly_horizontal_box_drawing("═══") is True - def test_is_all_horizontal_box_drawing_mixed(self) -> None: - """Mixed content returns False.""" - assert _is_all_horizontal_box_drawing("─A─") is False - assert _is_all_horizontal_box_drawing("│──") is False # vertical at start + def test_is_mostly_horizontal_box_drawing_with_corruption(self) -> None: + """Mostly horizontal with some corrupted chars returns True.""" + # 90% horizontal (9 out of 10) + assert _is_mostly_horizontal_box_drawing("─────────X") is True + # With replacement chars (like U+FFFD) + assert _is_mostly_horizontal_box_drawing("───\ufffd───") is True + + def test_is_mostly_horizontal_box_drawing_mixed(self) -> None: + """Mixed content below threshold returns False.""" + assert _is_mostly_horizontal_box_drawing("─A─") is False # 66% horizontal + assert _is_mostly_horizontal_box_drawing("│──") is False # vertical at start class TestRenderTerminalSvg: