{{! Renders `class` and/or `style` attributes for a background SVG span }}
{{~#*inline "svg_bg_tspan_attrs"}}
{{~#scope bg=bg classes="" styles=""}}
{{~#if inverted~}}
{{~#if (eq (typeof fg) "null")~}}
{{~set @bg 7 ~}} {{! indexed white }}
{{~else~}}
{{~set @bg fg ~}}
{{~/if~}}
{{~/if~}}
{{~#set @classes append=true}}
{{~#if dimmed}} dimmed{{/if~}}
{{~#if blink}} blink{{/if~}}
{{! Here, we intentionally use the *foreground* class }}
{{~#if (eq (typeof @bg) "number")}} fg{{@bg}}{{/if~}}
{{/set~}}
{{~set @classes (trim @classes)~}}
{{~#if (eq (typeof @bg) "string")}}
{{~#set @styles append=true}}fill: {{@bg}}; stroke: {{@bg}};{{/set~}}
{{~/if~}}
{{#if @classes}} class="{{@classes}}"{{/if}}{{#if @styles}} style="{{@styles}}"{{/if}}
{{~/scope~}}
{{~/inline~}}
{{! Root template }}
{{~#*inline "root"}}
<!-- Created with {{{creator.name}}} v{{{creator.version}}} ({{{creator.repo}}}) -->
<svg viewBox="0 {{#if window}}-{{const.WINDOW_FRAME_HEIGHT}}{{else}}0{{/if}} {{width}} {{height}}" width="{{width}}" height="{{height}}" xmlns="http://www.w3.org/2000/svg">
{{>styles}}
{{>background}}
{{~>content}}
{{~#if scroll_animation}}
{{>scrollbar}}
{{>scroll_animation}}
{{/if}}
</svg>
{{/inline~}}
{{! CSS definitions }}
{{~#*inline "styles"}}
<style>
{{>common_styles_header}}
.container {
font: {{const.FONT_SIZE}}px {{#if embedded_font}}"{{embedded_font.family_name}}", monospace{{else}}{{font_family}}{{/if}};
line-height: {{round line_height digits=2}}px;
}
text { white-space: pre; text-decoration-thickness: 1px; }
.input-bg { fill: #fff; fill-opacity: 0.1; }
.input-hidden { display: none; }
.output-bg { stroke-width: 0.5; }
.output-sep { stroke: {{ palette.colors.white }}; stroke-width: 1; stroke-opacity: 0.35; }
{{~#if has_failures}}
.input-bg .input-failure { fill: #ff0041; fill-opacity: 0.15; }
.input-failure-hl { fill: #ff0041; fill-opacity: 1; }
{{/if}}
{{~#if scroll_animation}}
.scrollbar { fill: #fff; fill-opacity: 0.35; }
{{~/if}}
{{~#if line_numbers}}
.line-numbers { text-anchor: end; fill-opacity: 0.35; user-select: none; }
{{/if}}
{{>common_font_styles}}
.underline { text-decoration-line: underline; }
.strike { text-decoration-line: line-through; }
.underline.strike { text-decoration-line: underline line-through; }
.dimmed { fill-opacity: {{dim_opacity}}; }
.concealed { fill-opacity: 0 !important; }
@keyframes blink {
0% { fill-opacity: 1; stroke-opacity: 1; }
100% { fill-opacity: {{blink.opacity}}; stroke-opacity: {{blink.opacity}}; }
}
.blink { animation: {{mul 2 blink.interval}}s steps(2, jump-none) 0s infinite blink; }
{{~#if wrap}}
.hard-br { font-weight: bold; fill-opacity: 0.6; user-select: none; }
{{~/if}}
.fg0,.inv.fg-none { fill: {{ palette.colors.black }}; } .output-bg .fg0 { stroke: {{ palette.colors.black }}; }
.fg1 { fill: {{ palette.colors.red }}; } .output-bg .fg1 { stroke: {{ palette.colors.red }}; }
.fg2 { fill: {{ palette.colors.green }}; } .output-bg .fg2 { stroke: {{ palette.colors.green }}; }
.fg3 { fill: {{ palette.colors.yellow }}; } .output-bg .fg3 { stroke: {{ palette.colors.yellow }}; }
.fg4 { fill: {{ palette.colors.blue }}; } .output-bg .fg4 { stroke: {{ palette.colors.blue }}; }
.fg5 { fill: {{ palette.colors.magenta }}; } .output-bg .fg5 { stroke: {{ palette.colors.magenta }}; }
.fg6 { fill: {{ palette.colors.cyan }}; } .output-bg .fg6 { stroke: {{ palette.colors.cyan }}; }
.fg7 { fill: {{ palette.colors.white }}; } .output-bg .fg7 { stroke: {{ palette.colors.white }}; }
.fg8 { fill: {{ palette.intense_colors.black }}; } .output-bg .fg8 { stroke: {{ palette.intense_colors.black }}; }
.fg9 { fill: {{ palette.intense_colors.red }}; } .output-bg .fg9 { stroke: {{ palette.intense_colors.red }}; }
.fg10 { fill: {{ palette.intense_colors.green }}; } .output-bg .fg10 { stroke: {{ palette.intense_colors.green }}; }
.fg11 { fill: {{ palette.intense_colors.yellow }}; } .output-bg .fg11 { stroke: {{ palette.intense_colors.yellow }}; }
.fg12 { fill: {{ palette.intense_colors.blue }}; } .output-bg .fg12 { stroke: {{ palette.intense_colors.blue }}; }
.fg13 { fill: {{ palette.intense_colors.magenta }}; } .output-bg .fg13 { stroke: {{ palette.intense_colors.magenta }}; }
.fg14 { fill: {{ palette.intense_colors.cyan }}; } .output-bg .fg14 { stroke: {{ palette.intense_colors.cyan }}; }
.fg15 { fill: {{ palette.intense_colors.white }}; } .output-bg .fg15 { stroke: {{ palette.intense_colors.white }}; }
</style>
{{/inline~}}
{{~#*inline "content"}}
<svg{{#if scroll_animation}} id="scroll-container"{{/if}} x="0" y="{{const.WINDOW_PADDING}}" width="{{width}}" height="{{screen_height}}" viewBox="0 0 {{width}} {{screen_height}}">
{{#scope
x_pos=const.WINDOW_PADDING
input_x_pos=const.WINDOW_PADDING
y_pos=0
input_bg=""
output_bg=""
text_fg=""
hard_breaks=""
~}}
{{~#if line_numbers~}}
{{set @x_pos (add @x_pos const.LN_WIDTH const.LN_PADDING)}}
{{~/if~}}
{{~#if line_numbers~}}
{{~#if (eq line_numbers.scope "continuous")~}}
{{~set @input_x_pos @x_pos~}}
{{~/if~}}
{{~/if~}}
{{~#each interactions}}
{{~#if (not input.hidden)}}
{{! Render input background }}
{{~set @../y_pos (add @../y_pos ../const.USER_INPUT_PADDING)~}}
{{~#set @../input_bg append=true ~}}
{{! `y_pos` refers to the top of the text line, so for <rect>s we need to subtract the top margin }}
{{~#scope
top_y=(round (sub @../y_pos ../const.USER_INPUT_PADDING) digits=2)
input_height=(round (add (mul (count_lines input.text) ../line_height) (mul 2 ../const.USER_INPUT_PADDING)) digits=1)
~}}
<rect x="0" y="{{@top_y}}" width="100%" height="{{@input_height}}"{{#if failure}} class="input-failure"{{/if}}>
{{~#if failure~}}
<title>This command exited with non-zero code</title>
{{~/if~}}
</rect>
{{~#if failure~}}
<rect x="0" y="{{@top_y}}" width="2" height="{{@input_height}}" class="input-failure-hl" />
<rect x="100%" y="{{@top_y}}" width="2" height="{{@input_height}}" class="input-failure-hl" transform="translate(-2, 0)" />
{{~/if~}}
{{~/scope~}}
{{~/set~}}
{{~/if~}} {{! if (not input.hidden) }}
{{! The awkward newlines at the end of line <text>s are required for the text to be properly copyable }}
{{~#set @../text_fg append=true ~}}
<g class="input{{#if input.hidden}} input-hidden{{/if}}{{#if failure}} input-failure{{/if}}">
{{~#each (split_lines input.text)~}}
<g transform="translate({{@../../input_x_pos}} {{
round (add @../../y_pos ../../text_y_offset) digits=2
}})">{{#scope text_x_offset=0 sp="" ~}}
{{~#if @first~}}
{{~set @text_x_offset (round (mul (char_width ../input.prompt) ../../advance_width) digits=2)~}}
{{~set @sp " "~}}
<text class="prompt"{{#if ../../adjust_text_length}} textLength="{{@text_x_offset}}"{{/if}}>{{../input.prompt}}</text>
{{~/if~}}
<text x="{{@text_x_offset}}"{{#if ../../adjust_text_length}} textLength="{{
round (mul (add (char_width this) (char_width @sp)) ../../advance_width) digits=2
}}"{{/if}}>{{@sp}}{{this}}</text>
{{~/scope}}<text>
</text></g>
{{~#if (not ../input.hidden)~}}
{{~set @../../y_pos (add @../../y_pos ../../line_height)}}
{{~/if~}}
{{~/each~}}
</g>
{{~/set~}}
{{~#if (not input.hidden)~}}
{{~set @../y_pos (add @../y_pos ../const.USER_INPUT_PADDING ../const.BLOCK_MARGIN)}}
{{~/if~}}
{{! Render output backround and text }}
{{~#set @../text_fg append=true ~}}
{{~#if (and (not @first) input.hidden)~}}
{{~#scope line_y_pos=(round (sub @../y_pos (sub (div ../const.BLOCK_MARGIN 2) 1)) digits=1)~}}
<line class="output-sep" x1="0" y1="{{@line_y_pos}}" x2="100%" y2="{{@line_y_pos}}" />
{{~/scope~}}
{{~/if~}}
<g class="output">
{{~/set~}}
{{~#each output as |line|~}}
{{~#scope char_pos=0 line_fg="" line_bg="" ~}}
{{~#each line.spans as |span|~}}
{{~#set @../line_bg append=true ~}}
{{~#if (or (ne (typeof span.bg) "null") span.inverted)~}}
<rect x="{{
round (add @../../../x_pos (mul @../char_pos ../../../advance_width)) digits=2
}}" y="{{
round @../../../y_pos digits=2
}}" width="{{
round (mul (char_width span.text) ../../../advance_width) digits=2
}}" height="{{
round ../../../line_height digits=2
}}"{{>svg_bg_tspan_attrs span}}/>
{{~/if~}}
{{~/set~}}
{{! Render span foreground }}
{{~#set @../line_fg append=true ~}}
<text x="{{
round (mul @../char_pos ../../../advance_width) digits=2
}}"{{#if (and ../../../adjust_text_length (gt (char_width span.text) 1))}} textLength="{{
round (mul (char_width span.text) ../../../advance_width) digits=2
}}"{{/if}}{{>svg_tspan_attrs span}}>{{span.text}}</text>
{{~#if @last~}}
<text{{#if ../line.br}} class="hard-br"{{/if}}>
</text>
{{~/if~}}
{{~/set~}}
{{! Update char position on the line }}
{{~set @../char_pos (add @../char_pos (char_width span.text))~}}
{{~else~}}{{! The line may be empty }}
{{~/each~}}
{{~#scope mark=@root.wrap.hard_break_at.mark~}}
{{~#if (and line.br @mark)~}}
{{~#set @../../hard_breaks append=true~}}
<text x="{{
round (add @../../x_pos 8 (mul ../../advance_width @char_pos)) digits=2
}}" y="{{
round (add @../../y_pos ../../text_y_offset) digits=2
}}">{{@mark}}</text>
{{~/set~}}
{{~/if~}}
{{~/scope~}}
{{! Append collected `line_bg` to the overall background }}
{{~#set @../../output_bg append=true}}{{{@line_bg}}}{{/set~}}
{{! Append collected `line_fg` to the overall text }}
{{~#set @../../text_fg append=true ~}}
{{~#if @line_fg~}}
<g transform="translate({{@../../x_pos}} {{
round (add @../../y_pos ../../text_y_offset) digits=2
}})" clip-path="xywh(0 0 100% {{../../line_height}}px)">{{{@line_fg}}}</g>
{{~else~}}
{{! The line is empty. In this case, append an empty `<text>` so that the output is correctly parsed. }}
<text>
</text>
{{~/if~}}
{{~/set~}}
{{~/scope~}}
{{~set @../../y_pos (add @../../y_pos ../../line_height)~}}
{{~else~}}{{! The output may be empty }}
{{~/each~}}
{{~#set @../text_fg append=true }}</g>{{/set~}}
{{~#if (ne (len output) 0)}}
{{~set @../y_pos (add @../y_pos ../const.BLOCK_MARGIN)}}
{{~/if}}
{{~/each~}}
<g class="input-bg">{{{@input_bg}}}</g>
{{~#if @output_bg~}}
<g class="output-bg">{{{@output_bg}}}</g>
{{~/if~}}
{{#if line_numbers}}
{{>number_lines}}
{{/if~}}
{{~#if @hard_breaks}}<g class="container fg7 hard-br">{{{@hard_breaks}}}</g>{{/if~}}
<g class="container fg7">{{{@text_fg}}}</g>
{{~/scope~}}
</svg>
{{/inline~}}
{{~#*inline "number_lines"~}}
{{~#scope
x_pos=(add const.WINDOW_PADDING const.LN_WIDTH)
y_pos=0
line_number=1
~}}
<g class="container fg7 line-numbers">
{{~#each interactions}}
{{~#if (not input.hidden)}}
{{~set @../y_pos (add @../y_pos ../const.USER_INPUT_PADDING)~}}
{{~#if (eq ../line_numbers.scope "continuous")}}
{{~#each (range 0 (count_lines input.text))~}}
<text x="{{@../../x_pos}}" y="{{round (add @../../y_pos ../../text_y_offset) digits=2}}">{{add this @../../line_number}}</text>
{{~set @../../y_pos (add @../../y_pos ../../line_height)~}}
{{~/each~}}
{{~set @../line_number (add @../line_number (count_lines input.text))~}}
{{~else~}}
{{~set @../y_pos (add @../y_pos (mul (count_lines input.text) ../line_height))~}}
{{~/if~}}
{{~set @../y_pos (add @../y_pos ../const.USER_INPUT_PADDING ../const.BLOCK_MARGIN)~}}
{{~/if~}}
{{! Number lines in the output }}
{{~#if (eq ../line_numbers.scope "each_output")~}}
{{~set @../line_number 1 ~}}
{{~/if~}}
{{~#scope skip_line_number=false ~}}
{{~#each output as |line|~}}
{{~#if (not @../skip_line_number)~}}
<text x="{{@../../x_pos}}" y="{{round (add @../../y_pos ../../text_y_offset) digits=2}}">{{@../../line_number}}</text>
{{~set @../../line_number (add 1 @../../line_number)~}}
{{~else~}}
{{~#scope mark=@root.line_numbers.continued.mark~}}
{{~#if @mark~}}
<text x="{{add @../../x_pos 3}}" y="{{round (add @../../y_pos ../../text_y_offset) digits=2}}">{{@mark}}</text>
{{~/if~}}
{{~/scope~}}
{{~/if~}}
{{~set @../skip_line_number (and line.br (ne ../../line_numbers.continued "inherit"))~}}
{{~set @../../y_pos (add @../../y_pos ../../line_height)~}}
{{~/each~}}
{{~/scope~}}
{{~#if (gt (len output) 0)~}}
{{~set @../y_pos (add @../y_pos ../const.BLOCK_MARGIN)~}}
{{~/if~}}
{{~/each~}}
</g>
{{~/scope~}}
{{~/inline~}}
{{~#*inline "main"~}}
{{#scope
content_height=0
scroll_animation=null
line_height=line_height
advance_width=advance_width
adjust_text_length=true
text_y_offset=0
screen_height=0
height=0
width=width
line_number=1
}}
{{~#set @line_height}}{{>compute_line_height}}{{/set~}}
{{~#debug line_height=@line_height}}computed line height{{/debug~}}
{{~#set @content_height~}}
{{~>compute_content_height this line_height=@line_height ~}}
{{~/set~}}
{{~set @content_height (round @content_height digits=1)~}}
{{~#debug content_height=@content_height}}computed content height{{/debug~}}
{{~#if embedded_font~}}
{{~#scope metrics=null ~}}
{{~#set @metrics~}}
{{~>scale_font_metrics metrics=embedded_font.metrics font_size=const.FONT_SIZE ~}}
{{~/set~}}
{{~#if (eq @advance_width null)~}}
{{~set @advance_width (lookup @metrics "advance_width")~}}
{{~#debug metrics=@metrics}}using advance width from font metrics{{/debug~}}
{{~set @adjust_text_length false ~}}
{{~else~}}
{{! Scale advance width from ems to pixels }}
{{~#debug advance_width=@advance_width}}using user-provided advance width{{/debug~}}
{{~set @advance_width (mul @advance_width const.FONT_SIZE)~}}
{{~/if~}}
{{!
Top and bottom marigns are (line_height - (ascent - descent)) / 2. To get an Y offset, we need to add the top margin
and the ascent, i.e. (line_height + ascent + descent) / 2.
}}
{{~set @text_y_offset (div (add @line_height (lookup @metrics "ascent") (lookup @metrics "descent")) 2)~}}
{{~#debug metrics=@metrics text_y_offset=@text_y_offset}}computed Y offset for text baseline from font metrics{{/debug~}}
{{~/scope~}}
{{~else~}}
{{~#if (eq @advance_width null)~}}
{{~#debug}}using default advance width{{/debug~}}
{{~set @advance_width 8 ~}} {{! Set to a reasonable default: ~0.57em }}
{{~else~}}
{{! Scale advance width from ems to pixels }}
{{~set @advance_width (mul @advance_width const.FONT_SIZE)~}}
{{~/if~}}
{{! Use the above formula with heuristic values: ascent ~ 1em, descent ~ -0.3em. Hence, their sum is ~0.65em or ~9px. }}
{{~set @text_y_offset (div (add @line_height 9) 2)~}}
{{~#debug text_y_offset=@text_y_offset}}computed Y offset for text baseline heuristically{{/debug~}}
{{~/if~}}
{{~#if line_numbers~}}
{{! Adjust width to take line numbers into account }}
{{~set @width (add @width const.LN_WIDTH const.LN_PADDING)~}}
{{~/if~}}
{{~#if scroll~}}
{{~#set @scroll_animation~}}
{{~>compute_scroll_animation this width=@width content_height=@content_height~}}
{{~/set~}}
{{~/if~}}
{{~#if @scroll_animation~}}
{{set @screen_height scroll.max_height}}
{{~else~}}
{{set @screen_height @content_height}}
{{~/if~}}
{{~set @height (add @screen_height (mul const.WINDOW_PADDING 2))~}}
{{~#if window~}}
{{set @height (add @height const.WINDOW_FRAME_HEIGHT)}}
{{~/if~}}
{{>root (splat_vars this)~}} {{! <-- All rendering happens here }}
{{/scope}}
{{~/inline~}}
{{~>_common~}}