mirror of
https://github.com/daylinmorgan/yartsu.git
synced 2024-12-22 04:20:44 -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]
|
[flake8]
|
||||||
ignore = E203, E266, E501, W503, F403
|
ignore = E203, E266, E501, W503, F403, C901
|
||||||
exclude =
|
exclude =
|
||||||
.git,
|
.git,
|
||||||
__pycache__,
|
__pycache__,
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -64,7 +64,7 @@ theme-docs:
|
||||||
./scripts/theme-showcase-gen
|
./scripts/theme-showcase-gen
|
||||||
|
|
||||||
diff-docs:
|
diff-docs:
|
||||||
./scripts/code_svg_format_diff.py >docs/rich-diff.md
|
./scripts/rich-diff > docs/rich-diff.md
|
||||||
|
|
||||||
svg-docs:
|
svg-docs:
|
||||||
lolcat -F .5 -S 9 -f assets/logo.txt | yartsu -o assets/logo.svg
|
lolcat -F .5 -S 9 -f assets/logo.txt | yartsu -o assets/logo.svg
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
## Versions
|
## Versions
|
||||||
|
|
||||||
- Rich: 12.4.4
|
- Rich: 12.4.4
|
||||||
- Yartsu: 22.6b2.dev2+gabdbc1d
|
- Yartsu: 22.6b4.dev3+gddae53c.d20220617
|
||||||
|
|
||||||
## Diff
|
## CONSOLE_SVG_FORMAT Diff
|
||||||
|
|
||||||
```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" 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">
|
+<svg class="rich-terminal shadow" viewBox="0 0 {width} {height}" xmlns="http://www.w3.org/2000/svg">
|
||||||
<!-- Generated with Rich https://www.textualize.io -->
|
<!-- Generated with Rich https://www.textualize.io -->
|
||||||
|
@ -35,37 +35,58 @@
|
||||||
font-style: bold;
|
font-style: bold;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}}
|
}}
|
||||||
-
|
@@ -32,6 +29,10 @@
|
||||||
.{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;
|
font-family: arial;
|
||||||
}}
|
}}
|
||||||
-
|
|
||||||
+ .shadow {{
|
+ .shadow {{
|
||||||
+ -webkit-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( 5px 10px 15px rgba(0, 0, 0, .7));
|
+ filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7));
|
||||||
+ }}
|
+ }}
|
||||||
{styles}
|
{styles}
|
||||||
</style>
|
</style>
|
||||||
-
|
|
||||||
<defs>
|
@@ -43,7 +44,7 @@
|
||||||
<clipPath id="{unique_id}-clip-terminal">
|
|
||||||
<rect x="0" y="0" width="{terminal_width}" height="{terminal_height}" />
|
|
||||||
</clipPath>
|
|
||||||
{lines}
|
|
||||||
</defs>
|
</defs>
|
||||||
-
|
|
||||||
{chrome}
|
{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}
|
{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
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import inspect
|
||||||
from importlib.metadata import version
|
from importlib.metadata import version
|
||||||
|
|
||||||
from rich._export_format import CONSOLE_SVG_FORMAT as DEFAULT
|
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._export_format import CONSOLE_SVG_FORMAT as MODIFIED
|
||||||
|
from yartsu.console import Console as YartsuConsole
|
||||||
|
|
||||||
MARKDOWN_DOC = """
|
MARKDOWN_DOC = """
|
||||||
# Deviation From Rich
|
# Deviation From Rich
|
||||||
|
@ -14,10 +17,16 @@ MARKDOWN_DOC = """
|
||||||
- Rich: {rich_version}
|
- Rich: {rich_version}
|
||||||
- Yartsu: {yartsu_version}
|
- Yartsu: {yartsu_version}
|
||||||
|
|
||||||
## Diff
|
## CONSOLE_SVG_FORMAT Diff
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
{diff}
|
{export_format_diff}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Console.export_svg Diff
|
||||||
|
|
||||||
|
```diff
|
||||||
|
{export_svg_diff}
|
||||||
```
|
```
|
||||||
|
|
||||||
AUTO-GENERATED by code_svg_format_diff.py"""
|
AUTO-GENERATED by code_svg_format_diff.py"""
|
||||||
|
@ -45,8 +54,12 @@ def main():
|
||||||
MARKDOWN_DOC.format(
|
MARKDOWN_DOC.format(
|
||||||
rich_version=version("rich"),
|
rich_version=version("rich"),
|
||||||
yartsu_version=version("yartsu"),
|
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-style: bold;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
.{unique_id}-matrix {{
|
.{unique_id}-matrix {{
|
||||||
font-family: Fira Code, monospace;
|
font-family: Fira Code, monospace;
|
||||||
font-size: {char_height}px;
|
font-size: {char_height}px;
|
||||||
line-height: {line_height}px;
|
line-height: {line_height}px;
|
||||||
font-variant-east-asian: full-width;
|
font-variant-east-asian: full-width;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
.{unique_id}-title {{
|
.{unique_id}-title {{
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-family: arial;
|
font-family: arial;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
.shadow {{
|
.shadow {{
|
||||||
-webkit-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( 5px 10px 15px rgba(0, 0, 0, .7));
|
filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7));
|
||||||
}}
|
}}
|
||||||
{styles}
|
{styles}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="{unique_id}-clip-terminal">
|
<clipPath id="{unique_id}-clip-terminal">
|
||||||
<rect x="0" y="0" width="{terminal_width}" height="{terminal_height}" />
|
<rect x="0" y="0" width="{terminal_width}" height="{terminal_height}" />
|
||||||
</clipPath>
|
</clipPath>
|
||||||
{lines}
|
{lines}
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
{chrome}
|
{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}
|
{backgrounds}
|
||||||
<g class="{unique_id}-matrix">
|
<g class="{unique_id}-matrix">
|
||||||
{matrix}
|
{matrix}
|
||||||
|
|
|
@ -5,13 +5,13 @@ from argparse import SUPPRESS, FileType, Namespace
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from rich.__main__ import make_test_card
|
from rich.__main__ import make_test_card
|
||||||
from rich.console import Console
|
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
from ._export_format import CONSOLE_SVG_FORMAT
|
from ._export_format import CONSOLE_SVG_FORMAT
|
||||||
from ._run_cmd import run_cmd
|
from ._run_cmd import run_cmd
|
||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
from .argparse import ArgumentParser
|
from .argparse import ArgumentParser
|
||||||
|
from .console import Console
|
||||||
from .term import term
|
from .term import term
|
||||||
from .themes import THEMES
|
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