Stream code¶
Generates an animation of a code snippet being written character by character.
Highlights:
- Uses a custom
renderer
function to create each frame of the animation. - Propagates
formatter
,max_line_length
, andmax_line_number
to the customrenderer
function.
from textwrap import dedent
import numpy as np
from PIL import Image, ImageDraw
from pygments import lex
from pygments.formatters import ImageFormatter
from pygments.lexers import get_lexer_by_name
from streamjoy import stream
def _custom_format(
formatter: ImageFormatter,
tokensource: list[tuple],
max_line_length: int = None,
max_line_number: int = None,
) -> Image:
formatter._create_drawables(tokensource)
formatter._draw_line_numbers()
max_line_length = max_line_length or formatter.maxlinelength
max_line_number = max_line_number or formatter.maxlineno
image = Image.new(
"RGB",
formatter._get_image_size(max_line_length, max_line_number),
formatter.background_color,
)
formatter._paint_line_number_bg(image)
draw = ImageDraw.Draw(image)
# Highlight
if formatter.hl_lines:
x = (
formatter.image_pad
+ formatter.line_number_width
- formatter.line_number_pad
+ 1
)
recth = formatter._get_line_height()
rectw = image.size[0] - x
for linenumber in formatter.hl_lines:
y = formatter._get_line_y(linenumber - 1)
draw.rectangle([(x, y), (x + rectw, y + recth)], fill=formatter.hl_color)
for pos, value, font, text_fg, text_bg in formatter.drawables:
if text_bg:
text_size = draw.textsize(text=value, font=font)
draw.rectangle(
[pos[0], pos[1], pos[0] + text_size[0], pos[1] + text_size[1]],
fill=text_bg,
)
draw.text(pos, value, font=font, fill=text_fg)
return np.asarray(image)
def render_frame(
code: str,
formatter: ImageFormatter,
max_line_length: int = None,
max_line_number: int = None,
) -> Image:
lexer = get_lexer_by_name("python")
return _custom_format(
formatter,
lex(code, lexer),
max_line_length=max_line_length,
max_line_number=max_line_number,
)
if __name__ == "__main__":
code = dedent(
"""
import matplotlib.pyplot as plt
import numpy as np
from streamjoy import stream, wrap_matplotlib
@wrap_matplotlib()
def plot_frame(i):
x = np.linspace(0, 2, 1000)
y = np.sin(2 * np.pi * (x - 0.01 * i))
fig, ax = plt.subplots()
ax.plot(x, y)
return fig
stream(list(range(10)), uri="sine_wave.mp4", renderer=plot_frame)
"""
)
formatter = ImageFormatter(
image_format="gif",
line_pad=8,
line_number_bg=None,
line_number_fg=None,
encoding="utf-8",
)
longest_line = max(code.splitlines(), key=len) + " " * 12
max_line_length, _ = formatter.fonts.get_text_size(longest_line)
max_line_number = code.count("\n") + 1
items = [code[:i] for i in range(0, len(code) + 3, 3)]
stream(
items,
ending_pause=20,
uri="stream_code.gif",
renderer=render_frame,
formatter=formatter,
max_line_length=max_line_length,
max_line_number=max_line_number,
)