Fix cursor/background vertical alignment in SVG screenshots
- Remove dominant-baseline: text-before-edge (has Safari compatibility issues) - Use separate y positions for rect (top of cell) and text (baseline) - rect_y = padding + row * line_height (top of cell) - text_y = rect_y + font_size (alphabetic baseline position) This ensures background rects and text are properly aligned across all browsers, fixing the half-line vertical offset on cursor blocks.
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 796 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "textual-webterm"
|
name = "textual-webterm"
|
||||||
version = "0.3.11"
|
version = "0.3.12"
|
||||||
description = "Serve terminal sessions over the web"
|
description = "Serve terminal sessions over the web"
|
||||||
authors = ["Will McGugan <will@textualize.io>"]
|
authors = ["Will McGugan <will@textualize.io>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -138,6 +138,9 @@ def render_terminal_svg(
|
|||||||
parts.append(f"<title>{_escape_xml(title)}</title>")
|
parts.append(f"<title>{_escape_xml(title)}</title>")
|
||||||
|
|
||||||
# Style definitions
|
# Style definitions
|
||||||
|
# Note: We use alphabetic baseline (default) and offset text y by font_size
|
||||||
|
# to align text top with rect top. This is more compatible across browsers
|
||||||
|
# than dominant-baseline: text-before-edge which has Safari issues.
|
||||||
parts.append("<defs><style>")
|
parts.append("<defs><style>")
|
||||||
parts.append(
|
parts.append(
|
||||||
f".terminal-bg {{ fill: {background}; }}"
|
f".terminal-bg {{ fill: {background}; }}"
|
||||||
@@ -146,7 +149,6 @@ def render_terminal_svg(
|
|||||||
f"font-size: {font_size}px; "
|
f"font-size: {font_size}px; "
|
||||||
f"fill: {foreground}; "
|
f"fill: {foreground}; "
|
||||||
f"white-space: pre; "
|
f"white-space: pre; "
|
||||||
f"dominant-baseline: text-before-edge; "
|
|
||||||
f"text-rendering: optimizeLegibility; "
|
f"text-rendering: optimizeLegibility; "
|
||||||
f"}}"
|
f"}}"
|
||||||
f".bold {{ font-weight: bold; }}"
|
f".bold {{ font-weight: bold; }}"
|
||||||
@@ -167,8 +169,11 @@ def render_terminal_svg(
|
|||||||
# Render each row - use explicit x position for EACH character
|
# Render each row - use explicit x position for EACH character
|
||||||
# to ensure pixel-perfect alignment regardless of font metrics
|
# to ensure pixel-perfect alignment regardless of font metrics
|
||||||
for row_idx, row_data in enumerate(screen_buffer):
|
for row_idx, row_data in enumerate(screen_buffer):
|
||||||
# With dominant-baseline: text-before-edge, text top aligns to y
|
# rect_y is the top of the cell
|
||||||
y = 10 + row_idx * actual_line_height
|
rect_y = 10 + row_idx * actual_line_height
|
||||||
|
# text_y is the baseline position (alphabetic baseline = bottom of lowercase letters)
|
||||||
|
# For most fonts, baseline is roughly at font_size from top of em box
|
||||||
|
text_y = rect_y + font_size
|
||||||
|
|
||||||
if not row_data:
|
if not row_data:
|
||||||
continue
|
continue
|
||||||
@@ -205,7 +210,7 @@ def render_terminal_svg(
|
|||||||
if bg != background:
|
if bg != background:
|
||||||
bg_width = char_cols * char_width
|
bg_width = char_cols * char_width
|
||||||
row_bg_rects.append(
|
row_bg_rects.append(
|
||||||
f'<rect x="{x:.1f}" y="{y:.1f}" '
|
f'<rect x="{x:.1f}" y="{rect_y:.1f}" '
|
||||||
f'width="{bg_width:.1f}" height="{actual_line_height:.1f}" '
|
f'width="{bg_width:.1f}" height="{actual_line_height:.1f}" '
|
||||||
f'fill="{bg}"/>'
|
f'fill="{bg}"/>'
|
||||||
)
|
)
|
||||||
@@ -234,7 +239,7 @@ def render_terminal_svg(
|
|||||||
if row_bg_rects or row_tspans:
|
if row_bg_rects or row_tspans:
|
||||||
parts.extend(row_bg_rects)
|
parts.extend(row_bg_rects)
|
||||||
if row_tspans:
|
if row_tspans:
|
||||||
parts.append(f'<text y="{y:.1f}">')
|
parts.append(f'<text y="{text_y:.1f}">')
|
||||||
parts.extend(row_tspans)
|
parts.extend(row_tspans)
|
||||||
parts.append("</text>")
|
parts.append("</text>")
|
||||||
|
|
||||||
|
|||||||
@@ -152,8 +152,6 @@ class TestRenderTerminalSvg:
|
|||||||
def test_css_properties(self) -> None:
|
def test_css_properties(self) -> None:
|
||||||
"""SVG includes essential CSS properties for proper rendering."""
|
"""SVG includes essential CSS properties for proper rendering."""
|
||||||
svg = render_terminal_svg([], width=80, height=24)
|
svg = render_terminal_svg([], width=80, height=24)
|
||||||
# Check for proper baseline alignment
|
|
||||||
assert "dominant-baseline: text-before-edge" in svg
|
|
||||||
# Check for legibility optimization
|
# Check for legibility optimization
|
||||||
assert "text-rendering: optimizeLegibility" in svg
|
assert "text-rendering: optimizeLegibility" in svg
|
||||||
# Check for monospace font
|
# Check for monospace font
|
||||||
|
|||||||
Reference in New Issue
Block a user