Path 1: 2 calls (0.67)

'Rich' (2)

None (2)

True (2)

'\n \n ...

1def export_svg(
2        self,
3        *,
4        title: str = "Rich",
5        theme: Optional[TerminalTheme] = None,
6        clear: bool = True,
7        code_format: str = CONSOLE_SVG_FORMAT,
8        font_aspect_ratio: float = 0.61,
9        unique_id: Optional[str] = None,
10    ) -> str:
11        """
12        Generate an SVG from the console contents (requires record=True in Console constructor).
13
14        Args:
15            title (str, optional): The title of the tab in the output image
16            theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal
17            clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``
18            code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables
19                into the string in order to form the final SVG output. The default template used and the variables
20                injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable.
21            font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format``
22                string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font).
23                If you aren't specifying a different font inside ``code_format``, you probably don't need this.
24            unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node
25                ids). If not set, this defaults to a computed value based on the recorded content.
26        """
27
28        from rich.cells import cell_len
29
30        style_cache: Dict[Style, str] = {}
31
32        def get_svg_style(style: Style) -> str:
33            """Convert a Style to CSS rules for SVG."""
34            if style in style_cache:
35                return style_cache[style]
36            css_rules = []
37            color = (
38                _theme.foreground_color
39                if (style.color is None or style.color.is_default)
40                else style.color.get_truecolor(_theme)
41            )
42            bgcolor = (
43                _theme.background_color
44                if (style.bgcolor is None or style.bgcolor.is_default)
45                else style.bgcolor.get_truecolor(_theme)
46            )
47            if style.reverse:
48                color, bgcolor = bgcolor, color
49            if style.dim:
50                color = blend_rgb(color, bgcolor, 0.4)
51            css_rules.append(f"fill: {color.hex}")
52            if style.bold:
53                css_rules.append("font-weight: bold")
54            if style.italic:
55                css_rules.append("font-style: italic;")
56            if style.underline:
57                css_rules.append("text-decoration: underline;")
58            if style.strike:
59                css_rules.append("text-decoration: line-through;")
60
61            css = ";".join(css_rules)
62            style_cache[style] = css
63            return css
64
65        _theme = theme or SVG_EXPORT_THEME
66
67        width = self.width
68        char_height = 20
69        char_width = char_height * font_aspect_ratio
70        line_height = char_height * 1.22
71
72        margin_top = 1
73        margin_right = 1
74        margin_bottom = 1
75        margin_left = 1
76
77        padding_top = 40
78        padding_right = 8
79        padding_bottom = 8
80        padding_left = 8
81
82        padding_width = padding_left + padding_right
83        padding_height = padding_top + padding_bottom
84        margin_width = margin_left + margin_right
85        margin_height = margin_top + margin_bottom
86
87        text_backgrounds: List[str] = []
88        text_group: List[str] = []
89        classes: Dict[str, int] = {}
90        style_no = 1
91
92        def escape_text(text: str) -> str:
93            """HTML escape text and replace spaces with nbsp."""
94            return escape(text).replace(" ", " ")
95
96        def make_tag(
97            name: str, content: Optional[str] = None, **attribs: object
98        ) -> str:
99            """Make a tag from name, content, and attributes."""
100
101            def stringify(value: object) -> str:
102                if isinstance(value, (float)):
103                    return format(value, "g")
104                return str(value)
105
106            tag_attribs = " ".join(
107                f'{k.lstrip("_").replace("_", "-")}="{stringify(v)}"'
108                for k, v in attribs.items()
109            )
110            return (
111                f"<{name} {tag_attribs}>{content}</{name}>"
112                if content
113                else f"<{name} {tag_attribs}/>"
114            )
115
116        with self._record_buffer_lock:
117            segments = list(Segment.filter_control(self._record_buffer))
118            if clear:
119                self._record_buffer.clear()
120
121        if unique_id is None:
122            unique_id = "terminal-" + str(
123                zlib.adler32(
124                    ("".join(repr(segment) for segment in segments)).encode(
125                        "utf-8",
126                        "ignore",
127                    )
128                    + title.encode("utf-8", "ignore")
129                )
130            )
131        y = 0
132        for y, line in enumerate(Segment.split_and_crop_lines(segments, length=width)):
133            x = 0
134            for text, style, _control in line:
135                style = style or Style()
136                rules = get_svg_style(style)
137                if rules not in classes:
138                    classes[rules] = style_no
139                    style_no += 1
140                class_name = f"r{classes[rules]}"
141
142                if style.reverse:
143                    has_background = True
144                    background = (
145                        _theme.foreground_color.hex
146                        if style.color is None
147                        else style.color.get_truecolor(_theme).hex
148                    )
149                else:
150                    bgcolor = style.bgcolor
151                    has_background = bgcolor is not None and not bgcolor.is_default
152                    background = (
153                        _theme.background_color.hex
154                        if style.bgcolor is None
155                        else style.bgcolor.get_truecolor(_theme).hex
156                    )
157
158                text_length = cell_len(text)
159                if has_background:
160                    text_backgrounds.append(
161                        make_tag(
162                            "rect",
163                            fill=background,
164                            x=x * char_width,
165                            y=y * line_height + 1.5,
166                            width=char_width * text_length,
167                            height=line_height + 0.25,
168                            shape_rendering="crispEdges",
169                        )
170                    )
171
172                if text != " " * len(text):
173                    text_group.append(
174                        make_tag(
175                            "text",
176                            escape_text(text),
177                            _class=f"{unique_id}-{class_name}",
178                            x=x * char_width,
179                            y=y * line_height + char_height,
180                            textLength=char_width * len(text),
181                            clip_path=f"url(#{unique_id}-line-{y})",
182                        )
183                    )
184                x += cell_len(text)
185
186        line_offsets = [line_no * line_height + 1.5 for line_no in range(y)]
187        lines = "\n".join(
188            f"""<clipPath id="{unique_id}-line-{line_no}">
189    {make_tag("rect", x=0, y=offset, width=char_width * width, height=line_height + 0.25)}
190            </clipPath>"""
191            for line_no, offset in enumerate(line_offsets)
192        )
193
194        styles = "\n".join(
195            f".{unique_id}-r{rule_no} {{ {css} }}" for css, rule_no in classes.items()
196        )
197        backgrounds = "".join(text_backgrounds)
198        matrix = "".join(text_group)
199
200        terminal_width = ceil(width * char_width + padding_width)
201        terminal_height = (y + 1) * line_height + padding_height
202        chrome = make_tag(
203            "rect",
204            fill=_theme.background_color.hex,
205            stroke="rgba(255,255,255,0.35)",
206            stroke_width="1",
207            x=margin_left,
208            y=margin_top,
209            width=terminal_width,
210            height=terminal_height,
211            rx=8,
212        )
213
214        title_color = _theme.foreground_color.hex
215        if title:
216            chrome += make_tag(
217                "text",
218                escape_text(title),
219                _class=f"{unique_id}-title",
220                fill=title_color,
221                text_anchor="middle",
222                x=terminal_width // 2,
223                y=margin_top + char_height + 6,
224            )
225        chrome += f"""
226            <g transform="translate(26,22)">
227            <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
228            <circle cx="22" cy="0" r="7" fill="#febc2e"/>
229            <circle cx="44" cy="0" r="7" fill="#28c840"/>
230            </g>
231        """
232
233        svg = code_format.format(
234            unique_id=unique_id,
235            char_width=char_width,
236            char_height=char_height,
237            line_height=line_height,
238            terminal_width=char_width * width - 1,
239            terminal_height=(y + 1) * line_height - 1,
240            width=terminal_width + margin_width,
241            height=terminal_height + margin_height,
242            terminal_x=margin_left + padding_left,
243            terminal_y=margin_top + padding_top,
244            styles=styles,
245            chrome=chrome,
246            backgrounds=backgrounds,
247            matrix=matrix,
248            lines=lines,
249        )
250        return svg
            

Path 2: 1 calls (0.33)

'Rich' (1)

None (1)

True (1)

'\n \n ...

1def export_svg(
2        self,
3        *,
4        title: str = "Rich",
5        theme: Optional[TerminalTheme] = None,
6        clear: bool = True,
7        code_format: str = CONSOLE_SVG_FORMAT,
8        font_aspect_ratio: float = 0.61,
9        unique_id: Optional[str] = None,
10    ) -> str:
11        """
12        Generate an SVG from the console contents (requires record=True in Console constructor).
13
14        Args:
15            title (str, optional): The title of the tab in the output image
16            theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal
17            clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``
18            code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables
19                into the string in order to form the final SVG output. The default template used and the variables
20                injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable.
21            font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format``
22                string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font).
23                If you aren't specifying a different font inside ``code_format``, you probably don't need this.
24            unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node
25                ids). If not set, this defaults to a computed value based on the recorded content.
26        """
27
28        from rich.cells import cell_len
29
30        style_cache: Dict[Style, str] = {}
31
32        def get_svg_style(style: Style) -> str:
33            """Convert a Style to CSS rules for SVG."""
34            if style in style_cache:
35                return style_cache[style]
36            css_rules = []
37            color = (
38                _theme.foreground_color
39                if (style.color is None or style.color.is_default)
40                else style.color.get_truecolor(_theme)
41            )
42            bgcolor = (
43                _theme.background_color
44                if (style.bgcolor is None or style.bgcolor.is_default)
45                else style.bgcolor.get_truecolor(_theme)
46            )
47            if style.reverse:
48                color, bgcolor = bgcolor, color
49            if style.dim:
50                color = blend_rgb(color, bgcolor, 0.4)
51            css_rules.append(f"fill: {color.hex}")
52            if style.bold:
53                css_rules.append("font-weight: bold")
54            if style.italic:
55                css_rules.append("font-style: italic;")
56            if style.underline:
57                css_rules.append("text-decoration: underline;")
58            if style.strike:
59                css_rules.append("text-decoration: line-through;")
60
61            css = ";".join(css_rules)
62            style_cache[style] = css
63            return css
64
65        _theme = theme or SVG_EXPORT_THEME
66
67        width = self.width
68        char_height = 20
69        char_width = char_height * font_aspect_ratio
70        line_height = char_height * 1.22
71
72        margin_top = 1
73        margin_right = 1
74        margin_bottom = 1
75        margin_left = 1
76
77        padding_top = 40
78        padding_right = 8
79        padding_bottom = 8
80        padding_left = 8
81
82        padding_width = padding_left + padding_right
83        padding_height = padding_top + padding_bottom
84        margin_width = margin_left + margin_right
85        margin_height = margin_top + margin_bottom
86
87        text_backgrounds: List[str] = []
88        text_group: List[str] = []
89        classes: Dict[str, int] = {}
90        style_no = 1
91
92        def escape_text(text: str) -> str:
93            """HTML escape text and replace spaces with nbsp."""
94            return escape(text).replace(" ", "&#160;")
95
96        def make_tag(
97            name: str, content: Optional[str] = None, **attribs: object
98        ) -> str:
99            """Make a tag from name, content, and attributes."""
100
101            def stringify(value: object) -> str:
102                if isinstance(value, (float)):
103                    return format(value, "g")
104                return str(value)
105
106            tag_attribs = " ".join(
107                f'{k.lstrip("_").replace("_", "-")}="{stringify(v)}"'
108                for k, v in attribs.items()
109            )
110            return (
111                f"<{name} {tag_attribs}>{content}</{name}>"
112                if content
113                else f"<{name} {tag_attribs}/>"
114            )
115
116        with self._record_buffer_lock:
117            segments = list(Segment.filter_control(self._record_buffer))
118            if clear:
119                self._record_buffer.clear()
120
121        if unique_id is None:
122            unique_id = "terminal-" + str(
123                zlib.adler32(
124                    ("".join(repr(segment) for segment in segments)).encode(
125                        "utf-8",
126                        "ignore",
127                    )
128                    + title.encode("utf-8", "ignore")
129                )
130            )
131        y = 0
132        for y, line in enumerate(Segment.split_and_crop_lines(segments, length=width)):
133            x = 0
134            for text, style, _control in line:
135                style = style or Style()
136                rules = get_svg_style(style)
137                if rules not in classes:
138                    classes[rules] = style_no
139                    style_no += 1
140                class_name = f"r{classes[rules]}"
141
142                if style.reverse:
143                    has_background = True
144                    background = (
145                        _theme.foreground_color.hex
146                        if style.color is None
147                        else style.color.get_truecolor(_theme).hex
148                    )
149                else:
150                    bgcolor = style.bgcolor
151                    has_background = bgcolor is not None and not bgcolor.is_default
152                    background = (
153                        _theme.background_color.hex
154                        if style.bgcolor is None
155                        else style.bgcolor.get_truecolor(_theme).hex
156                    )
157
158                text_length = cell_len(text)
159                if has_background:
160                    text_backgrounds.append(
161                        make_tag(
162                            "rect",
163                            fill=background,
164                            x=x * char_width,
165                            y=y * line_height + 1.5,
166                            width=char_width * text_length,
167                            height=line_height + 0.25,
168                            shape_rendering="crispEdges",
169                        )
170                    )
171
172                if text != " " * len(text):
173                    text_group.append(
174                        make_tag(
175                            "text",
176                            escape_text(text),
177                            _class=f"{unique_id}-{class_name}",
178                            x=x * char_width,
179                            y=y * line_height + char_height,
180                            textLength=char_width * len(text),
181                            clip_path=f"url(#{unique_id}-line-{y})",
182                        )
183                    )
184                x += cell_len(text)
185
186        line_offsets = [line_no * line_height + 1.5 for line_no in range(y)]
187        lines = "\n".join(
188            f"""<clipPath id="{unique_id}-line-{line_no}">
189    {make_tag("rect", x=0, y=offset, width=char_width * width, height=line_height + 0.25)}
190            </clipPath>"""
191            for line_no, offset in enumerate(line_offsets)
192        )
193
194        styles = "\n".join(
195            f".{unique_id}-r{rule_no} {{ {css} }}" for css, rule_no in classes.items()
196        )
197        backgrounds = "".join(text_backgrounds)
198        matrix = "".join(text_group)
199
200        terminal_width = ceil(width * char_width + padding_width)
201        terminal_height = (y + 1) * line_height + padding_height
202        chrome = make_tag(
203            "rect",
204            fill=_theme.background_color.hex,
205            stroke="rgba(255,255,255,0.35)",
206            stroke_width="1",
207            x=margin_left,
208            y=margin_top,
209            width=terminal_width,
210            height=terminal_height,
211            rx=8,
212        )
213
214        title_color = _theme.foreground_color.hex
215        if title:
216            chrome += make_tag(
217                "text",
218                escape_text(title),
219                _class=f"{unique_id}-title",
220                fill=title_color,
221                text_anchor="middle",
222                x=terminal_width // 2,
223                y=margin_top + char_height + 6,
224            )
225        chrome += f"""
226            <g transform="translate(26,22)">
227            <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
228            <circle cx="22" cy="0" r="7" fill="#febc2e"/>
229            <circle cx="44" cy="0" r="7" fill="#28c840"/>
230            </g>
231        """
232
233        svg = code_format.format(
234            unique_id=unique_id,
235            char_width=char_width,
236            char_height=char_height,
237            line_height=line_height,
238            terminal_width=char_width * width - 1,
239            terminal_height=(y + 1) * line_height - 1,
240            width=terminal_width + margin_width,
241            height=terminal_height + margin_height,
242            terminal_x=margin_left + padding_left,
243            terminal_y=margin_top + padding_top,
244            styles=styles,
245            chrome=chrome,
246            backgrounds=backgrounds,
247            matrix=matrix,
248            lines=lines,
249        )
250        return svg