-
Notifications
You must be signed in to change notification settings - Fork 140
/
Copy pathscreen.go
125 lines (106 loc) · 2.7 KB
/
screen.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package main
import (
"crypto/sha1"
"encoding/hex"
"image"
"image/color"
"image/png"
"io"
"sync"
"github.com/fogleman/gg"
)
// Screen is a canvas that accepts input and can render an image
type Screen struct {
bgImage image.Image
width int
height int
fontPath string
bufMux sync.RWMutex
buf []rune
}
// NewScreen creates a Screen with a background image
func NewScreen(bgImagePath, fontPath string) (*Screen, error) {
bgImage, err := gg.LoadImage(bgImagePath)
if err != nil {
return nil, err
}
rect := bgImage.Bounds()
return &Screen{
bgImage: bgImage,
width: rect.Max.X - rect.Min.X,
height: rect.Max.Y - rect.Min.Y,
fontPath: fontPath,
}, nil
}
// Add adds a character to the screen's buffer. Passing in '\b' will delete the last character
// if it exists.
func (s *Screen) Add(ch rune) {
s.bufMux.Lock()
defer s.bufMux.Unlock()
// turn enter into space
if ch == '\n' {
ch = ' '
}
// temp hack to clear the screen when long enough
if len(s.buf) > 300 {
s.buf = []rune{ch}
return
}
if ch == '\b' {
if len(s.buf) > 0 {
s.buf = s.buf[:len(s.buf)-1]
}
} else {
s.buf = append(s.buf, ch)
}
}
// Render renders the screen's buffer and returns an etag
func (s *Screen) Render(w io.Writer) (string, error) {
s.bufMux.RLock()
message := string(s.buf)
s.bufMux.RUnlock()
frame1, err := s.renderString(message)
if err != nil {
return "", err
}
err = png.Encode(w, frame1)
if err != nil {
return "", err
}
// frame2, err := s.renderString(message + "|")
// if err != nil {
// return "", err
// }
// palettedImage1 := image.NewPaletted(frame1.Bounds(), palette.Plan9)
// draw.FloydSteinberg.Draw(palettedImage1, frame1.Bounds(), frame1, image.ZP)
// palettedImage2 := image.NewPaletted(frame2.Bounds(), palette.Plan9)
// draw.FloydSteinberg.Draw(palettedImage2, frame2.Bounds(), frame2, image.ZP)
// gif.EncodeAll(w, &gif.GIF{
// Image: []*image.Paletted{
// palettedImage1,
// palettedImage2,
// },
// Delay: []int{50, 50},
// })
return etag(message), nil
}
func (s *Screen) renderString(message string) (image.Image, error) {
dc := gg.NewContext(s.width, s.height)
dc.DrawImage(s.bgImage, 0, 0)
if err := dc.LoadFontFace(s.fontPath, 0.055*float64(s.width)); err != nil {
return nil, err
}
marginHorizontal := 0.05 * float64(s.width)
marginVertical := 0.1 * float64(s.height)
x := marginHorizontal
y := marginVertical
maxWidth := float64(s.width) - marginHorizontal - marginHorizontal
dc.SetColor(color.Black)
dc.DrawStringWrapped(message, x, y, 0, 0, maxWidth, 1.6, gg.AlignLeft)
return dc.Image(), nil
}
func etag(s string) string {
hasher := sha1.New()
hasher.Write([]byte(s))
return hex.EncodeToString(hasher.Sum(nil))
}