Scale box-drawing characters vertically to fill line height
Box-drawing characters (│┃║┌┐└┘├┤etc) are designed to connect between lines but the font's em-box is smaller than our line-height (14px vs 16.8px), creating visible gaps. Solution: Render box-drawing characters as separate text elements with a vertical scale transform of 1.2 (matching line-height) to stretch them to fill the full cell height and connect properly. This fixes disconnected vertical lines and corners in TUI applications.
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
+1
-1
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "textual-webterm"
|
||||
version = "0.3.13"
|
||||
version = "0.3.14"
|
||||
description = "Serve terminal sessions over the web"
|
||||
authors = ["Will McGugan <will@textualize.io>"]
|
||||
license = "MIT"
|
||||
|
||||
@@ -50,6 +50,25 @@ FONT_SIZE = 14
|
||||
LINE_HEIGHT = 1.2
|
||||
CHAR_WIDTH = 8 # Width of monospace character at 14px (typically ~0.57 ratio)
|
||||
|
||||
# Box drawing characters that need vertical scaling to fill line height
|
||||
# These are designed to connect between lines but the font's em-box is smaller
|
||||
# than our line height, creating gaps
|
||||
BOX_DRAWING_CHARS = frozenset(
|
||||
# Light and heavy box drawing (U+2500-U+257F)
|
||||
"─━│┃┄┅┆┇┈┉┊┋┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋"
|
||||
# Double box drawing
|
||||
"═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬"
|
||||
# Rounded corners
|
||||
"╭╮╯╰"
|
||||
# Light and heavy dashed (U+2571-U+257F)
|
||||
"\u2571\u2572\u2573╴╵╶╷╸╹╺╻╼╽╾╿"
|
||||
)
|
||||
|
||||
|
||||
def _is_box_drawing(char: str) -> bool:
|
||||
"""Check if character is a box-drawing character that needs scaling."""
|
||||
return len(char) == 1 and char in BOX_DRAWING_CHARS
|
||||
|
||||
|
||||
class CharData(TypedDict):
|
||||
"""Character data from pyte screen buffer."""
|
||||
@@ -232,6 +251,19 @@ def render_terminal_svg(
|
||||
if classes:
|
||||
attrs.append(f'class="{" ".join(classes)}"')
|
||||
|
||||
# Box-drawing characters need vertical scaling to fill line height
|
||||
# Render them as separate text elements with transform
|
||||
if _is_box_drawing(char_data):
|
||||
# Scale vertically by line_height ratio, anchored at top of cell
|
||||
# The transform scales around (x, rect_y) to stretch the glyph
|
||||
fill_attr = f' fill="{fg}"' if fg != foreground else ""
|
||||
class_attr = f' class="{" ".join(classes)}"' if classes else ""
|
||||
row_bg_rects.append(
|
||||
f'<text x="{x:.1f}" y="{text_y:.1f}" '
|
||||
f'transform="translate(0,{rect_y:.1f}) scale(1,{line_height}) translate(0,{-rect_y:.1f})"'
|
||||
f'{fill_attr}{class_attr}>{_escape_xml(char_data)}</text>'
|
||||
)
|
||||
else:
|
||||
row_tspans.append(f'<tspan {" ".join(attrs)}>{_escape_xml(char_data)}</tspan>')
|
||||
|
||||
col += char_cols
|
||||
|
||||
@@ -328,6 +328,22 @@ class TestRenderTerminalSvg:
|
||||
# Text tspan with yellow
|
||||
assert f'fill="{ANSI_COLORS["yellow"]}"' in svg
|
||||
|
||||
def test_box_drawing_vertical_scale(self) -> None:
|
||||
"""Box-drawing characters are scaled vertically to fill line height."""
|
||||
buffer = [[self._char("│")]] # Vertical line
|
||||
svg = render_terminal_svg(buffer, width=80, height=24)
|
||||
# Box drawing chars rendered with transform for vertical scaling
|
||||
assert 'scale(1,1.2)' in svg
|
||||
# Should be a separate text element, not a tspan
|
||||
assert '<text x="' in svg
|
||||
|
||||
def test_box_drawing_corners(self) -> None:
|
||||
"""Box-drawing corner characters are scaled."""
|
||||
buffer = [[self._char("┌"), self._char("┐")]]
|
||||
svg = render_terminal_svg(buffer, width=80, height=24)
|
||||
# Both corners should have scale transforms
|
||||
assert svg.count('scale(1,1.2)') == 2
|
||||
|
||||
def test_unicode_text(self) -> None:
|
||||
"""Unicode text is preserved."""
|
||||
buffer = self._make_buffer(["你好世界"])
|
||||
|
||||
Reference in New Issue
Block a user