mirror of
https://github.com/daylinmorgan/yartsu.git
synced 2024-12-21 20:10:45 -06:00
feat: patch console to improve drop-shadow
This commit is contained in:
parent
21600f0b17
commit
13f4e50281
7 changed files with 331 additions and 35 deletions
2
.flake8
2
.flake8
|
@ -1,5 +1,5 @@
|
|||
[flake8]
|
||||
ignore = E203, E266, E501, W503, F403
|
||||
ignore = E203, E266, E501, W503, F403, C901
|
||||
exclude =
|
||||
.git,
|
||||
__pycache__,
|
||||
|
|
2
Makefile
2
Makefile
|
@ -64,7 +64,7 @@ theme-docs:
|
|||
./scripts/theme-showcase-gen
|
||||
|
||||
diff-docs:
|
||||
./scripts/code_svg_format_diff.py >docs/rich-diff.md
|
||||
./scripts/rich-diff > docs/rich-diff.md
|
||||
|
||||
svg-docs:
|
||||
lolcat -F .5 -S 9 -f assets/logo.txt | yartsu -o assets/logo.svg
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
## Versions
|
||||
|
||||
- Rich: 12.4.4
|
||||
- Yartsu: 22.6b2.dev2+gabdbc1d
|
||||
- Yartsu: 22.6b4.dev3+gddae53c.d20220617
|
||||
|
||||
## Diff
|
||||
## CONSOLE_SVG_FORMAT Diff
|
||||
|
||||
```diff
|
||||
---
|
||||
+++
|
||||
@@ -1,47 +1,43 @@
|
||||
@@ -1,20 +1,17 @@
|
||||
-<svg class="rich-terminal" viewBox="0 0 {width} {height}" xmlns="http://www.w3.org/2000/svg">
|
||||
+<svg class="rich-terminal shadow" viewBox="0 0 {width} {height}" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Generated with Rich https://www.textualize.io -->
|
||||
|
@ -35,37 +35,58 @@
|
|||
font-style: bold;
|
||||
font-weight: 700;
|
||||
}}
|
||||
-
|
||||
.{unique_id}-matrix {{
|
||||
font-family: Fira Code, monospace;
|
||||
font-size: {char_height}px;
|
||||
line-height: {line_height}px;
|
||||
font-variant-east-asian: full-width;
|
||||
}}
|
||||
-
|
||||
.{unique_id}-title {{
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
@@ -32,6 +29,10 @@
|
||||
font-family: arial;
|
||||
}}
|
||||
-
|
||||
|
||||
+ .shadow {{
|
||||
+ -webkit-filter: drop-shadow( 5px 10px 15px rgba(0, 0, 0, .7));
|
||||
+ filter: drop-shadow( 5px 10px 15px rgba(0, 0, 0, .7));
|
||||
+ -webkit-filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7));
|
||||
+ filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7));
|
||||
+ }}
|
||||
{styles}
|
||||
</style>
|
||||
-
|
||||
<defs>
|
||||
<clipPath id="{unique_id}-clip-terminal">
|
||||
<rect x="0" y="0" width="{terminal_width}" height="{terminal_height}" />
|
||||
</clipPath>
|
||||
{lines}
|
||||
|
||||
@@ -43,7 +44,7 @@
|
||||
</defs>
|
||||
-
|
||||
|
||||
{chrome}
|
||||
<g transform="translate({terminal_x}, {terminal_y})" clip-path="url(#{unique_id}-clip-terminal)">
|
||||
- <g transform="translate({terminal_x}, {terminal_y})" clip-path="url(#{unique_id}-clip-terminal)">
|
||||
+ <g transform="translate({terminal_x}, {terminal_y}) scale(.95)" clip-path="url(#{unique_id}-clip-terminal)">
|
||||
{backgrounds}
|
||||
<g class="{unique_id}-matrix">
|
||||
{matrix}
|
||||
|
||||
```
|
||||
|
||||
## Console.export_svg Diff
|
||||
|
||||
```diff
|
||||
---
|
||||
+++
|
||||
@@ -64,9 +64,9 @@
|
||||
line_height = char_height * 1.22
|
||||
|
||||
margin_top = 1
|
||||
- margin_right = 1
|
||||
- margin_bottom = 1
|
||||
- margin_left = 1
|
||||
+ margin_right = char_width * 5 / 6
|
||||
+ margin_bottom = 20 * 5 / 3
|
||||
+ margin_left = char_width * 5 / 6
|
||||
|
||||
padding_top = 40
|
||||
padding_right = 8
|
||||
@@ -214,8 +214,8 @@
|
||||
x=terminal_width // 2,
|
||||
y=margin_top + char_height + 6,
|
||||
)
|
||||
- chrome += f"""
|
||||
- <g transform="translate(26,22)">
|
||||
+ chrome += """
|
||||
+ <g transform="translate(32,22)">
|
||||
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
|
||||
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
|
||||
<circle cx="44" cy="0" r="7" fill="#28c840"/>
|
||||
|
||||
```
|
||||
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import inspect
|
||||
from importlib.metadata import version
|
||||
|
||||
from rich._export_format import CONSOLE_SVG_FORMAT as DEFAULT
|
||||
from rich.console import Console as RichConsole
|
||||
|
||||
from yartsu._export_format import CONSOLE_SVG_FORMAT as MODIFIED
|
||||
from yartsu.console import Console as YartsuConsole
|
||||
|
||||
MARKDOWN_DOC = """
|
||||
# Deviation From Rich
|
||||
|
@ -14,10 +17,16 @@ MARKDOWN_DOC = """
|
|||
- Rich: {rich_version}
|
||||
- Yartsu: {yartsu_version}
|
||||
|
||||
## Diff
|
||||
## CONSOLE_SVG_FORMAT Diff
|
||||
|
||||
```diff
|
||||
{diff}
|
||||
{export_format_diff}
|
||||
```
|
||||
|
||||
## Console.export_svg Diff
|
||||
|
||||
```diff
|
||||
{export_svg_diff}
|
||||
```
|
||||
|
||||
AUTO-GENERATED by code_svg_format_diff.py"""
|
||||
|
@ -45,8 +54,12 @@ def main():
|
|||
MARKDOWN_DOC.format(
|
||||
rich_version=version("rich"),
|
||||
yartsu_version=version("yartsu"),
|
||||
diff=unidiff_output(DEFAULT, MODIFIED),
|
||||
)
|
||||
export_format_diff=unidiff_output(DEFAULT, MODIFIED),
|
||||
export_svg_diff=unidiff_output(
|
||||
inspect.getsource(RichConsole.export_svg),
|
||||
inspect.getsource(YartsuConsole.export_svg),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -16,31 +16,36 @@ CONSOLE_SVG_FORMAT = """\
|
|||
font-style: bold;
|
||||
font-weight: 700;
|
||||
}}
|
||||
|
||||
.{unique_id}-matrix {{
|
||||
font-family: Fira Code, monospace;
|
||||
font-size: {char_height}px;
|
||||
line-height: {line_height}px;
|
||||
font-variant-east-asian: full-width;
|
||||
}}
|
||||
|
||||
.{unique_id}-title {{
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
font-family: arial;
|
||||
}}
|
||||
|
||||
.shadow {{
|
||||
-webkit-filter: drop-shadow( 5px 10px 15px rgba(0, 0, 0, .7));
|
||||
filter: drop-shadow( 5px 10px 15px rgba(0, 0, 0, .7));
|
||||
-webkit-filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7));
|
||||
filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7));
|
||||
}}
|
||||
{styles}
|
||||
</style>
|
||||
|
||||
<defs>
|
||||
<clipPath id="{unique_id}-clip-terminal">
|
||||
<rect x="0" y="0" width="{terminal_width}" height="{terminal_height}" />
|
||||
</clipPath>
|
||||
{lines}
|
||||
</defs>
|
||||
|
||||
{chrome}
|
||||
<g transform="translate({terminal_x}, {terminal_y})" clip-path="url(#{unique_id}-clip-terminal)">
|
||||
<g transform="translate({terminal_x}, {terminal_y}) scale(.95)" clip-path="url(#{unique_id}-clip-terminal)">
|
||||
{backgrounds}
|
||||
<g class="{unique_id}-matrix">
|
||||
{matrix}
|
||||
|
|
|
@ -5,13 +5,13 @@ from argparse import SUPPRESS, FileType, Namespace
|
|||
from pathlib import Path
|
||||
|
||||
from rich.__main__ import make_test_card
|
||||
from rich.console import Console
|
||||
from rich.text import Text
|
||||
|
||||
from ._export_format import CONSOLE_SVG_FORMAT
|
||||
from ._run_cmd import run_cmd
|
||||
from ._version import __version__
|
||||
from .argparse import ArgumentParser
|
||||
from .console import Console
|
||||
from .term import term
|
||||
from .themes import THEMES
|
||||
|
||||
|
|
257
yartsu/console.py
Normal file
257
yartsu/console.py
Normal file
|
@ -0,0 +1,257 @@
|
|||
import zlib
|
||||
from html import escape
|
||||
from math import ceil
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from rich.color import blend_rgb
|
||||
from rich.console import Console as RichConsole
|
||||
from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
from rich.terminal_theme import SVG_EXPORT_THEME, TerminalTheme
|
||||
|
||||
from ._export_format import CONSOLE_SVG_FORMAT
|
||||
|
||||
|
||||
class Console(RichConsole):
|
||||
def export_svg(
|
||||
self,
|
||||
*,
|
||||
title: str = "Rich",
|
||||
theme: Optional[TerminalTheme] = None,
|
||||
clear: bool = True,
|
||||
code_format: str = CONSOLE_SVG_FORMAT,
|
||||
) -> str:
|
||||
"""
|
||||
Generate an SVG from the console contents (requires record=True in Console constructor).
|
||||
|
||||
Args:
|
||||
path (str): The path to write the SVG to.
|
||||
title (str): The title of the tab in the output image
|
||||
theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal
|
||||
clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``
|
||||
code_format (str): Format string used to generate the SVG. Rich will inject a number of variables
|
||||
into the string in order to form the final SVG output. The default template used and the variables
|
||||
injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable.
|
||||
"""
|
||||
|
||||
from rich.cells import cell_len
|
||||
|
||||
style_cache: Dict[Style, str] = {}
|
||||
|
||||
def get_svg_style(style: Style) -> str:
|
||||
"""Convert a Style to CSS rules for SVG."""
|
||||
if style in style_cache:
|
||||
return style_cache[style]
|
||||
css_rules = []
|
||||
color = (
|
||||
_theme.foreground_color
|
||||
if (style.color is None or style.color.is_default)
|
||||
else style.color.get_truecolor(_theme)
|
||||
)
|
||||
bgcolor = (
|
||||
_theme.background_color
|
||||
if (style.bgcolor is None or style.bgcolor.is_default)
|
||||
else style.bgcolor.get_truecolor(_theme)
|
||||
)
|
||||
if style.reverse:
|
||||
color, bgcolor = bgcolor, color
|
||||
if style.dim:
|
||||
color = blend_rgb(color, bgcolor, 0.4)
|
||||
css_rules.append(f"fill: {color.hex}")
|
||||
if style.bold:
|
||||
css_rules.append("font-weight: bold")
|
||||
if style.italic:
|
||||
css_rules.append("font-style: italic;")
|
||||
if style.underline:
|
||||
css_rules.append("text-decoration: underline;")
|
||||
if style.strike:
|
||||
css_rules.append("text-decoration: line-through;")
|
||||
|
||||
css = ";".join(css_rules)
|
||||
style_cache[style] = css
|
||||
return css
|
||||
|
||||
_theme = theme or SVG_EXPORT_THEME
|
||||
|
||||
width = self.width
|
||||
char_height = 20
|
||||
char_width = char_height * 0.61
|
||||
line_height = char_height * 1.22
|
||||
|
||||
margin_top = 1
|
||||
margin_right = char_width * 5 / 6
|
||||
margin_bottom = 20 * 5 / 3
|
||||
margin_left = char_width * 5 / 6
|
||||
|
||||
padding_top = 40
|
||||
padding_right = 8
|
||||
padding_bottom = 8
|
||||
padding_left = 8
|
||||
|
||||
padding_width = padding_left + padding_right
|
||||
padding_height = padding_top + padding_bottom
|
||||
margin_width = margin_left + margin_right
|
||||
margin_height = margin_top + margin_bottom
|
||||
|
||||
text_backgrounds: List[str] = []
|
||||
text_group: List[str] = []
|
||||
classes: Dict[str, int] = {}
|
||||
style_no = 1
|
||||
|
||||
def escape_text(text: str) -> str:
|
||||
"""HTML escape text and replace spaces with nbsp."""
|
||||
return escape(text).replace(" ", " ")
|
||||
|
||||
def make_tag(
|
||||
name: str, content: Optional[str] = None, **attribs: object
|
||||
) -> str:
|
||||
"""Make a tag from name, content, and attributes."""
|
||||
|
||||
def stringify(value: object) -> str:
|
||||
if isinstance(value, (float)):
|
||||
return format(value, "g")
|
||||
return str(value)
|
||||
|
||||
tag_attribs = " ".join(
|
||||
f'{k.lstrip("_").replace("_", "-")}="{stringify(v)}"'
|
||||
for k, v in attribs.items()
|
||||
)
|
||||
return (
|
||||
f"<{name} {tag_attribs}>{content}</{name}>"
|
||||
if content
|
||||
else f"<{name} {tag_attribs}/>"
|
||||
)
|
||||
|
||||
with self._record_buffer_lock:
|
||||
segments = list(Segment.filter_control(self._record_buffer))
|
||||
if clear:
|
||||
self._record_buffer.clear()
|
||||
|
||||
unique_id = "terminal-" + str(
|
||||
zlib.adler32(
|
||||
("".join(segment.text for segment in segments)).encode(
|
||||
"utf-8", "ignore"
|
||||
)
|
||||
+ title.encode("utf-8", "ignore")
|
||||
)
|
||||
)
|
||||
y = 0
|
||||
for y, line in enumerate(Segment.split_and_crop_lines(segments, length=width)):
|
||||
x = 0
|
||||
for text, style, _control in line:
|
||||
style = style or Style()
|
||||
rules = get_svg_style(style)
|
||||
if rules not in classes:
|
||||
classes[rules] = style_no
|
||||
style_no += 1
|
||||
class_name = f"r{classes[rules]}"
|
||||
|
||||
if style.reverse:
|
||||
has_background = True
|
||||
background = (
|
||||
_theme.foreground_color.hex
|
||||
if style.color is None
|
||||
else style.color.get_truecolor(_theme).hex
|
||||
)
|
||||
else:
|
||||
bgcolor = style.bgcolor
|
||||
has_background = bgcolor is not None and not bgcolor.is_default
|
||||
background = (
|
||||
_theme.background_color.hex
|
||||
if style.bgcolor is None
|
||||
else style.bgcolor.get_truecolor(_theme).hex
|
||||
)
|
||||
|
||||
text_length = cell_len(text)
|
||||
if has_background:
|
||||
text_backgrounds.append(
|
||||
make_tag(
|
||||
"rect",
|
||||
fill=background,
|
||||
x=x * char_width,
|
||||
y=y * line_height + 1.5,
|
||||
width=char_width * text_length,
|
||||
height=line_height + 0.25,
|
||||
shape_rendering="crispEdges",
|
||||
)
|
||||
)
|
||||
|
||||
if text != " " * len(text):
|
||||
text_group.append(
|
||||
make_tag(
|
||||
"text",
|
||||
escape_text(text),
|
||||
_class=f"{unique_id}-{class_name}",
|
||||
x=x * char_width,
|
||||
y=y * line_height + char_height,
|
||||
textLength=char_width * len(text),
|
||||
clip_path=f"url(#{unique_id}-line-{y})",
|
||||
)
|
||||
)
|
||||
x += cell_len(text)
|
||||
|
||||
line_offsets = [line_no * line_height + 1.5 for line_no in range(y)]
|
||||
lines = "\n".join(
|
||||
f"""<clipPath id="{unique_id}-line-{line_no}">
|
||||
{make_tag("rect", x=0, y=offset, width=char_width * width, height=line_height + 0.25)}
|
||||
</clipPath>"""
|
||||
for line_no, offset in enumerate(line_offsets)
|
||||
)
|
||||
|
||||
styles = "\n".join(
|
||||
f".{unique_id}-r{rule_no} {{ {css} }}" for css, rule_no in classes.items()
|
||||
)
|
||||
backgrounds = "".join(text_backgrounds)
|
||||
matrix = "".join(text_group)
|
||||
|
||||
terminal_width = ceil(width * char_width + padding_width)
|
||||
terminal_height = (y + 1) * line_height + padding_height
|
||||
chrome = make_tag(
|
||||
"rect",
|
||||
fill=_theme.background_color.hex,
|
||||
stroke="rgba(255,255,255,0.35)",
|
||||
stroke_width="1",
|
||||
x=margin_left,
|
||||
y=margin_top,
|
||||
width=terminal_width,
|
||||
height=terminal_height,
|
||||
rx=8,
|
||||
)
|
||||
|
||||
title_color = _theme.foreground_color.hex
|
||||
if title:
|
||||
chrome += make_tag(
|
||||
"text",
|
||||
escape_text(title),
|
||||
_class=f"{unique_id}-title",
|
||||
fill=title_color,
|
||||
text_anchor="middle",
|
||||
x=terminal_width // 2,
|
||||
y=margin_top + char_height + 6,
|
||||
)
|
||||
chrome += """
|
||||
<g transform="translate(32,22)">
|
||||
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
|
||||
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
|
||||
<circle cx="44" cy="0" r="7" fill="#28c840"/>
|
||||
</g>
|
||||
"""
|
||||
|
||||
svg = code_format.format(
|
||||
unique_id=unique_id,
|
||||
char_width=char_width,
|
||||
char_height=char_height,
|
||||
line_height=line_height,
|
||||
terminal_width=char_width * width - 1,
|
||||
terminal_height=(y + 1) * line_height - 1,
|
||||
width=terminal_width + margin_width,
|
||||
height=terminal_height + margin_height,
|
||||
terminal_x=margin_left + padding_left,
|
||||
terminal_y=margin_top + padding_top,
|
||||
styles=styles,
|
||||
chrome=chrome,
|
||||
backgrounds=backgrounds,
|
||||
matrix=matrix,
|
||||
lines=lines,
|
||||
)
|
||||
return svg
|
Loading…
Reference in a new issue