1
2
3
4
5
6 package recorder
7
8 import (
9 "bytes"
10 "encoding/base64"
11 "fmt"
12 "image"
13 "image/color"
14 "image/png"
15 "runtime"
16
17 "gonum.org/v1/plot/font"
18 "gonum.org/v1/plot/font/liberation"
19 "gonum.org/v1/plot/vg"
20 )
21
22 var _ vg.Canvas = (*Canvas)(nil)
23
24
25 type Canvas struct {
26
27
28 Actions []Action
29
30
31
32
33 KeepCaller bool
34
35
36
37
38 c vg.Canvas
39
40
41 fonts map[fontID]font.Face
42 cache *font.Cache
43 }
44
45 type fontID struct {
46 name string
47 size vg.Length
48 }
49
50
51
52
53 type Action interface {
54 Call() string
55 ApplyTo(vg.Canvas)
56 callerLocation() *callerLocation
57 }
58
59 type callerLocation struct {
60 haveCaller bool
61 file string
62 line int
63 }
64
65 func (l *callerLocation) set() {
66 _, l.file, l.line, l.haveCaller = runtime.Caller(3)
67 }
68
69 func (l callerLocation) String() string {
70 if !l.haveCaller {
71 return ""
72 }
73 return fmt.Sprintf("%s:%d ", l.file, l.line)
74 }
75
76
77 func (c *Canvas) Reset() {
78 c.Actions = c.Actions[:0]
79 }
80
81
82
83 func (c *Canvas) ReplayOn(dst vg.Canvas) error {
84 if c.fonts == nil {
85 c.fonts = make(map[fontID]font.Face)
86 }
87 if c.cache == nil {
88 c.cache = font.NewCache(liberation.Collection())
89 }
90 for _, a := range c.Actions {
91 fa, ok := a.(*FillString)
92 if !ok {
93 continue
94 }
95 f := fontID{name: fa.Font.Name(), size: fa.Size}
96 if _, exists := c.fonts[f]; !exists {
97 if !c.cache.Has(fa.Font) {
98 return fmt.Errorf("unknown font: %s", fa.Font.Typeface)
99 }
100 face := c.cache.Lookup(
101 fa.Font,
102 fa.Size,
103 )
104 c.fonts[f] = face
105 }
106 fa.fonts = c.fonts
107 }
108 for _, a := range c.Actions {
109 a.ApplyTo(dst)
110 }
111 return nil
112 }
113
114 func (c *Canvas) append(a Action) {
115 if c.c != nil {
116 a.ApplyTo(c)
117 }
118 if c.KeepCaller {
119 a.callerLocation().set()
120 }
121 c.Actions = append(c.Actions, a)
122 }
123
124
125 type SetLineWidth struct {
126 Width vg.Length
127
128 l callerLocation
129 }
130
131
132 func (c *Canvas) SetLineWidth(w vg.Length) {
133 c.append(&SetLineWidth{Width: w})
134 }
135
136
137 func (a *SetLineWidth) Call() string {
138 return fmt.Sprintf("%sSetLineWidth(%v)", a.l, a.Width)
139 }
140
141
142 func (a *SetLineWidth) ApplyTo(c vg.Canvas) {
143 c.SetLineWidth(a.Width)
144 }
145
146 func (a *SetLineWidth) callerLocation() *callerLocation {
147 return &a.l
148 }
149
150
151 type SetLineDash struct {
152 Dashes []vg.Length
153 Offsets vg.Length
154
155 l callerLocation
156 }
157
158
159 func (c *Canvas) SetLineDash(dashes []vg.Length, offs vg.Length) {
160 c.append(&SetLineDash{
161 Dashes: append([]vg.Length(nil), dashes...),
162 Offsets: offs,
163 })
164 }
165
166
167 func (a *SetLineDash) Call() string {
168 return fmt.Sprintf("%sSetLineDash(%#v, %v)", a.l, a.Dashes, a.Offsets)
169 }
170
171
172 func (a *SetLineDash) ApplyTo(c vg.Canvas) {
173 c.SetLineDash(a.Dashes, a.Offsets)
174 }
175
176 func (a *SetLineDash) callerLocation() *callerLocation {
177 return &a.l
178 }
179
180
181 type SetColor struct {
182 Color color.Color
183
184 l callerLocation
185 }
186
187
188 func (c *Canvas) SetColor(col color.Color) {
189 c.append(&SetColor{Color: col})
190 }
191
192
193 func (a *SetColor) Call() string {
194 return fmt.Sprintf("%sSetColor(%#v)", a.l, a.Color)
195 }
196
197
198 func (a *SetColor) ApplyTo(c vg.Canvas) {
199 c.SetColor(a.Color)
200 }
201
202 func (a *SetColor) callerLocation() *callerLocation {
203 return &a.l
204 }
205
206
207 type Rotate struct {
208 Angle float64
209
210 l callerLocation
211 }
212
213
214 func (c *Canvas) Rotate(a float64) {
215 c.append(&Rotate{Angle: a})
216 }
217
218
219 func (a *Rotate) Call() string {
220 return fmt.Sprintf("%sRotate(%v)", a.l, a.Angle)
221 }
222
223
224 func (a *Rotate) ApplyTo(c vg.Canvas) {
225 c.Rotate(a.Angle)
226 }
227
228 func (a *Rotate) callerLocation() *callerLocation {
229 return &a.l
230 }
231
232
233 type Translate struct {
234 Point vg.Point
235
236 l callerLocation
237 }
238
239
240 func (c *Canvas) Translate(pt vg.Point) {
241 c.append(&Translate{Point: pt})
242 }
243
244
245 func (a *Translate) Call() string {
246 return fmt.Sprintf("%sTranslate(%v, %v)", a.l, a.Point.X, a.Point.Y)
247 }
248
249
250 func (a *Translate) ApplyTo(c vg.Canvas) {
251 c.Translate(a.Point)
252 }
253
254 func (a *Translate) callerLocation() *callerLocation {
255 return &a.l
256 }
257
258
259 type Scale struct {
260 X, Y float64
261
262 l callerLocation
263 }
264
265
266 func (c *Canvas) Scale(x, y float64) {
267 c.append(&Scale{X: x, Y: y})
268 }
269
270
271 func (a *Scale) Call() string {
272 return fmt.Sprintf("%sScale(%v, %v)", a.l, a.X, a.Y)
273 }
274
275
276 func (a *Scale) ApplyTo(c vg.Canvas) {
277 c.Scale(a.X, a.Y)
278 }
279
280 func (a *Scale) callerLocation() *callerLocation {
281 return &a.l
282 }
283
284
285 type Push struct {
286 l callerLocation
287 }
288
289
290 func (c *Canvas) Push() {
291 c.append(&Push{})
292 }
293
294
295 func (a *Push) Call() string {
296 return fmt.Sprintf("%sPush()", a.l)
297 }
298
299
300 func (a *Push) ApplyTo(c vg.Canvas) {
301 c.Push()
302 }
303
304 func (a *Push) callerLocation() *callerLocation {
305 return &a.l
306 }
307
308
309 type Pop struct {
310 l callerLocation
311 }
312
313
314 func (c *Canvas) Pop() {
315 c.append(&Pop{})
316 }
317
318
319 func (a *Pop) Call() string {
320 return fmt.Sprintf("%sPop()", a.l)
321 }
322
323
324 func (a *Pop) ApplyTo(c vg.Canvas) {
325 c.Pop()
326 }
327
328 func (a *Pop) callerLocation() *callerLocation {
329 return &a.l
330 }
331
332
333 type Stroke struct {
334 Path vg.Path
335
336 l callerLocation
337 }
338
339
340 func (c *Canvas) Stroke(path vg.Path) {
341 c.append(&Stroke{Path: append(vg.Path(nil), path...)})
342 }
343
344
345 func (a *Stroke) Call() string {
346 return fmt.Sprintf("%sStroke(%#v)", a.l, a.Path)
347 }
348
349
350 func (a *Stroke) ApplyTo(c vg.Canvas) {
351 c.Stroke(a.Path)
352 }
353
354 func (a *Stroke) callerLocation() *callerLocation {
355 return &a.l
356 }
357
358
359 type Fill struct {
360 Path vg.Path
361
362 l callerLocation
363 }
364
365
366 func (c *Canvas) Fill(path vg.Path) {
367 c.append(&Fill{Path: append(vg.Path(nil), path...)})
368 }
369
370
371 func (a *Fill) Call() string {
372 return fmt.Sprintf("%sFill(%#v)", a.l, a.Path)
373 }
374
375
376 func (a *Fill) ApplyTo(c vg.Canvas) {
377 c.Fill(a.Path)
378 }
379
380 func (a *Fill) callerLocation() *callerLocation {
381 return &a.l
382 }
383
384
385 type FillString struct {
386 Font font.Font
387 Size vg.Length
388 Point vg.Point
389 String string
390
391 l callerLocation
392
393 fonts map[fontID]font.Face
394 }
395
396
397 func (c *Canvas) FillString(font font.Face, pt vg.Point, str string) {
398 c.append(&FillString{
399 Font: font.Font,
400 Size: font.Font.Size,
401 Point: pt,
402 String: str,
403 })
404 }
405
406
407 func (a *FillString) ApplyTo(c vg.Canvas) {
408 c.FillString(a.fonts[fontID{name: a.Font.Name(), size: a.Size}], a.Point, a.String)
409 }
410
411
412 func (a *FillString) Call() string {
413 return fmt.Sprintf("%sFillString(%q, %v, %v, %v, %q)", a.l, a.Font.Name(), a.Size, a.Point.X, a.Point.Y, a.String)
414 }
415
416 func (a *FillString) callerLocation() *callerLocation {
417 return &a.l
418 }
419
420
421 type DrawImage struct {
422 Rectangle vg.Rectangle
423 Image image.Image
424
425 l callerLocation
426 }
427
428
429 func (c *Canvas) DrawImage(rect vg.Rectangle, img image.Image) {
430 c.append(&DrawImage{
431 Rectangle: rect,
432 Image: img,
433 })
434 }
435
436
437 func (a *DrawImage) ApplyTo(c vg.Canvas) {
438 c.DrawImage(a.Rectangle, a.Image)
439 }
440
441
442 func (a *DrawImage) Call() string {
443 var buf bytes.Buffer
444 err := png.Encode(&buf, a.Image)
445 if err != nil {
446 panic(fmt.Errorf("recorder: error encoding image to PNG: %v", err))
447 }
448 b64 := base64.StdEncoding.EncodeToString(buf.Bytes())
449 return fmt.Sprintf("%sDrawImage(%#v, {%#v, IMAGE:%s})", a.l, a.Rectangle, a.Image.Bounds(), b64)
450 }
451
452 func (a *DrawImage) callerLocation() *callerLocation {
453 return &a.l
454 }
455
456
457 type Commenter interface {
458 Comment(string)
459 }
460
461 var _ Commenter = (*Canvas)(nil)
462
463
464 type Comment struct {
465 Text string
466
467 l callerLocation
468 }
469
470
471 func (c *Canvas) Comment(text string) {
472 c.append(&Comment{Text: text})
473 }
474
475
476 func (a *Comment) Call() string {
477 return fmt.Sprintf("%sComment(%q)", a.l, a.Text)
478 }
479
480
481 func (a *Comment) ApplyTo(c vg.Canvas) {
482 if c, ok := c.(Commenter); ok {
483 c.Comment(a.Text)
484 }
485 }
486
487 func (a *Comment) callerLocation() *callerLocation {
488 return &a.l
489 }
490
View as plain text