Skip to content

Commit

Permalink
refactor(svg): reduce final file size
Browse files Browse the repository at this point in the history
  • Loading branch information
MrMarble committed Mar 8, 2022
1 parent 295ef7c commit a6b79ca
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 39 deletions.
97 changes: 64 additions & 33 deletions internal/svg/svg.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

svg "github.com/ajstarks/svgo"
"github.com/hinshun/vt10x"
"github.com/mrmarble/termsvg/internal/uniqueid"
"github.com/mrmarble/termsvg/pkg/asciicast"
"github.com/mrmarble/termsvg/pkg/color"
"github.com/mrmarble/termsvg/pkg/css"
Expand All @@ -22,24 +23,66 @@ const (
type Canvas struct {
*svg.SVG
asciicast.Cast
width int
height int
bgColors map[string]byte
id *uniqueid.ID
width int
height int
colors map[string]string
}

type Output interface {
io.Writer
}

func createSVG(svg *svg.SVG, cast asciicast.Cast) {
canvas := &Canvas{SVG: svg, Cast: cast}
func Export(input asciicast.Cast, output Output) {
input.Compress() // to reduce the number of frames

createCanvas(svg.New(output), input)
}

func createCanvas(svg *svg.SVG, cast asciicast.Cast) {
canvas := &Canvas{SVG: svg, Cast: cast, id: uniqueid.New(), colors: make(map[string]string)}
canvas.width = cast.Header.Width * colWidth
canvas.height = cast.Header.Height * rowHeight

parseCast(canvas)
canvas.createWindow()
canvas.End()
}

func parseCast(c *Canvas) {
term := vt10x.New(vt10x.WithSize(c.Header.Width, c.Header.Height))

for _, event := range c.Events {
_, err := term.Write([]byte(event.EventData))
if err != nil {
panic(err)
}

for row := 0; row < c.Header.Height; row++ {
for col := 0; col < c.Header.Width; col++ {
cell := term.Cell(col, row)

c.getColors(cell)
}
}
}
}

func (c *Canvas) getColors(cell vt10x.Glyph) {
fg := color.GetColor(cell.FG)
bg := color.GetColor(cell.BG)

if _, ok := c.colors[fg]; !ok {
c.colors[fg] = c.id.String()
c.id.Next()
}

if _, ok := c.colors[bg]; !ok {
c.colors[bg] = c.id.String()
c.id.Next()
}
}

func (c *Canvas) paddedWidth() int {
return c.width + (padding << 1)
}
Expand Down Expand Up @@ -67,23 +110,29 @@ func (c *Canvas) createWindow() {
}

func (c *Canvas) addStyles() {
styles := css.CSS{
c.Gstyle(css.Rules{
"animation-duration": fmt.Sprintf("%.2fs", c.Header.Duration),
"animation-iteration-count": "infinite",
"animation-name": "k",
"animation-timing-function": "steps(1,end)",
"font-family": "Monaco,Consolas,Menlo,'Bitstream Vera Sans Mono','Powerline Symbols',monospace",
"font-size": "20px",
}.String())

colors := css.Blocks{}
for color, class := range c.colors {
colors = append(colors, css.Block{Selector: fmt.Sprintf(".%s", class), Rules: css.Rules{"fill": color}})
}

c.Gstyle(styles.Compile())
c.Style("text/css", generateKeyframes(c.Cast, int32(c.paddedWidth())))
styles := generateKeyframes(c.Cast, int32(c.paddedWidth()))
styles += colors.String()
c.Style("text/css", styles)
c.Group(fmt.Sprintf(`transform="translate(%d,%d)"`, padding, padding*headerSize))
}

func (c *Canvas) createFrames() {
term := vt10x.New(vt10x.WithSize(c.Header.Width, c.Header.Height))
c.bgColors = map[string]byte{}

for i, event := range c.Events {
_, err := term.Write([]byte(event.EventData))
if err != nil {
Expand All @@ -104,7 +153,7 @@ func (c *Canvas) createFrames() {
if cell.Char == ' ' || cell.FG != lastColor {
if frame != "" {
c.Text(lastColummn*colWidth,
row*rowHeight, frame, fmt.Sprintf(`fill="%v"`, getColor(lastColor)), c.applyBG(cell.BG))
row*rowHeight, frame, fmt.Sprintf(`class="%s"`, c.colors[color.GetColor(lastColor)]), c.applyBG(cell.BG))

frame = ""
}
Expand All @@ -122,55 +171,37 @@ func (c *Canvas) createFrames() {
}

if strings.TrimSpace(frame) != "" {
c.Text(lastColummn*colWidth, row*rowHeight, frame, fmt.Sprintf(`fill="%v"`, getColor(lastColor)))
c.Text(lastColummn*colWidth, row*rowHeight, frame, fmt.Sprintf(`class="%s"`, c.colors[color.GetColor(lastColor)]))
}
}
c.Gend()
}
}

func Export(input asciicast.Cast, output Output) {
input.Compress() // to reduce the number of frames

createSVG(svg.New(output), input)
}

func (c *Canvas) addBG(bg vt10x.Color) {
if bg != vt10x.DefaultBG {
if _, ok := c.bgColors[fmt.Sprint(bg)]; !ok {
if _, ok := c.colors[fmt.Sprint(bg)]; !ok {
c.Def()
c.Filter(fmt.Sprint(bg))
c.FeFlood(svg.Filterspec{Result: "bg"}, getColor(bg), 1.0)
c.FeFlood(svg.Filterspec{Result: "bg"}, color.GetColor(bg), 1.0)
c.FeMerge([]string{`bg`, `SourceGraphic`})
c.Fend()
c.DefEnd()
c.bgColors[fmt.Sprint(bg)] = 1
c.colors[fmt.Sprint(bg)] = ""
}
}
}

func (c *Canvas) applyBG(bg vt10x.Color) string {
if bg != vt10x.DefaultBG {
if _, ok := c.bgColors[fmt.Sprint(bg)]; ok {
if _, ok := c.colors[fmt.Sprint(bg)]; ok {
return fmt.Sprintf(`filter="url(#%s)"`, fmt.Sprint(bg))
}
}

return ""
}

func getColor(c vt10x.Color) string {
colorStr := ""

if c.ANSI() {
colorStr = color.ToHex(color.AnsiToColor(uint32(c)))
} else {
colorStr = color.ToHex(color.AnsiToColor(uint32(vt10x.LightGrey)))
}

return colorStr
}

func generateKeyframes(cast asciicast.Cast, width int32) string {
css := "@keyframes k {"
for i, frame := range cast.Events {
Expand Down
1 change: 1 addition & 0 deletions internal/svg/svg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func BenchmarkExport(b *testing.B) {

for i := 0; i < b.N; i++ {
var output bytes.Buffer

svg.Export(*cast, &output)
}
}
12 changes: 6 additions & 6 deletions internal/svg/testdata/TestExportOutput.golden
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,24 @@
<g style="animation-duration:3.35s;animation-iteration-count:infinite;animation-name:k;animation-timing-function:steps(1,end);font-family:Monaco,Consolas,Menlo,'Bitstream Vera Sans Mono','Powerline Symbols',monospace;font-size:20px">
<style type="text/css">
<![CDATA[
@keyframes k {79.807%{transform:translateX(-0px)}82.298%{transform:translateX(-2383px)}87.777%{transform:translateX(-4766px)}92.767%{transform:translateX(-7149px)}100.000%{transform:translateX(-9532px)}}
@keyframes k {79.807%{transform:translateX(-0px)}82.298%{transform:translateX(-2383px)}87.777%{transform:translateX(-4766px)}92.767%{transform:translateX(-7149px)}100.000%{transform:translateX(-9532px)}}.a{fill:#c0c0c0}
]]>
</style>
<g transform="translate(20,60)" >
<g transform="translate(0)">
<text x="0" y="0" fill="#e5e5e5" >h</text>
<text x="0" y="0" class="a" >h</text>
</g>
<g transform="translate(2383)">
<text x="0" y="0" fill="#e5e5e5" >he</text>
<text x="0" y="0" class="a" >he</text>
</g>
<g transform="translate(4766)">
<text x="0" y="0" fill="#e5e5e5" >hel</text>
<text x="0" y="0" class="a" >hel</text>
</g>
<g transform="translate(7149)">
<text x="0" y="0" fill="#e5e5e5" >hell</text>
<text x="0" y="0" class="a" >hell</text>
</g>
<g transform="translate(9532)">
<text x="0" y="0" fill="#e5e5e5" >hello</text>
<text x="0" y="0" class="a" >hello</text>
</g>
</g>
</g>
Expand Down

0 comments on commit a6b79ca

Please sign in to comment.