// Copyright ©2015 The Gonum Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package recorder provides support for vector graphics serialization. package recorder // import "gonum.org/v1/plot/vg/recorder" import ( "bytes" "encoding/base64" "fmt" "image" "image/color" "image/png" "runtime" "gonum.org/v1/plot/font" "gonum.org/v1/plot/font/liberation" "gonum.org/v1/plot/vg" ) var _ vg.Canvas = (*Canvas)(nil) // Canvas implements vg.Canvas operation serialization. type Canvas struct { // Actions holds a log of all methods called on // the canvas. Actions []Action // KeepCaller indicates whether the Canvas will // retain runtime caller location for the actions. // This includes source filename and line number. KeepCaller bool // c holds a backing vg.Canvas. If c is non-nil // then method calls to the Canvas will be // reflected to c. c vg.Canvas // fonts holds a collection of font/size descriptions. fonts map[fontID]font.Face cache *font.Cache } type fontID struct { name string size vg.Length } // Action is a vector graphics action as defined by the // vg.Canvas interface. Each method of Canvas has a // corresponding Action type. type Action interface { Call() string ApplyTo(vg.Canvas) callerLocation() *callerLocation } type callerLocation struct { haveCaller bool file string line int } func (l *callerLocation) set() { _, l.file, l.line, l.haveCaller = runtime.Caller(3) } func (l callerLocation) String() string { if !l.haveCaller { return "" } return fmt.Sprintf("%s:%d ", l.file, l.line) } // Reset resets the Canvas to the base state. func (c *Canvas) Reset() { c.Actions = c.Actions[:0] } // ReplayOn applies the set of Actions recorded by the Recorder onto // the destination Canvas. func (c *Canvas) ReplayOn(dst vg.Canvas) error { if c.fonts == nil { c.fonts = make(map[fontID]font.Face) } if c.cache == nil { c.cache = font.NewCache(liberation.Collection()) } for _, a := range c.Actions { fa, ok := a.(*FillString) if !ok { continue } f := fontID{name: fa.Font.Name(), size: fa.Size} if _, exists := c.fonts[f]; !exists { if !c.cache.Has(fa.Font) { return fmt.Errorf("unknown font: %s", fa.Font.Typeface) } face := c.cache.Lookup( fa.Font, fa.Size, ) c.fonts[f] = face } fa.fonts = c.fonts } for _, a := range c.Actions { a.ApplyTo(dst) } return nil } func (c *Canvas) append(a Action) { if c.c != nil { a.ApplyTo(c) } if c.KeepCaller { a.callerLocation().set() } c.Actions = append(c.Actions, a) } // SetLineWidth corresponds to the vg.Canvas.SetWidth method. type SetLineWidth struct { Width vg.Length l callerLocation } // SetLineWidth implements the SetLineWidth method of the vg.Canvas interface. func (c *Canvas) SetLineWidth(w vg.Length) { c.append(&SetLineWidth{Width: w}) } // Call returns the method call that generated the action. func (a *SetLineWidth) Call() string { return fmt.Sprintf("%sSetLineWidth(%v)", a.l, a.Width) } // ApplyTo applies the action to the given vg.Canvas. func (a *SetLineWidth) ApplyTo(c vg.Canvas) { c.SetLineWidth(a.Width) } func (a *SetLineWidth) callerLocation() *callerLocation { return &a.l } // SetLineDash corresponds to the vg.Canvas.SetLineDash method. type SetLineDash struct { Dashes []vg.Length Offsets vg.Length l callerLocation } // SetLineDash implements the SetLineDash method of the vg.Canvas interface. func (c *Canvas) SetLineDash(dashes []vg.Length, offs vg.Length) { c.append(&SetLineDash{ Dashes: append([]vg.Length(nil), dashes...), Offsets: offs, }) } // Call returns the method call that generated the action. func (a *SetLineDash) Call() string { return fmt.Sprintf("%sSetLineDash(%#v, %v)", a.l, a.Dashes, a.Offsets) } // ApplyTo applies the action to the given vg.Canvas. func (a *SetLineDash) ApplyTo(c vg.Canvas) { c.SetLineDash(a.Dashes, a.Offsets) } func (a *SetLineDash) callerLocation() *callerLocation { return &a.l } // SetColor corresponds to the vg.Canvas.SetColor method. type SetColor struct { Color color.Color l callerLocation } // SetColor implements the SetColor method of the vg.Canvas interface. func (c *Canvas) SetColor(col color.Color) { c.append(&SetColor{Color: col}) } // Call returns the method call that generated the action. func (a *SetColor) Call() string { return fmt.Sprintf("%sSetColor(%#v)", a.l, a.Color) } // ApplyTo applies the action to the given vg.Canvas. func (a *SetColor) ApplyTo(c vg.Canvas) { c.SetColor(a.Color) } func (a *SetColor) callerLocation() *callerLocation { return &a.l } // Rotate corresponds to the vg.Canvas.Rotate method. type Rotate struct { Angle float64 l callerLocation } // Rotate implements the Rotate method of the vg.Canvas interface. func (c *Canvas) Rotate(a float64) { c.append(&Rotate{Angle: a}) } // Call returns the method call that generated the action. func (a *Rotate) Call() string { return fmt.Sprintf("%sRotate(%v)", a.l, a.Angle) } // ApplyTo applies the action to the given vg.Canvas. func (a *Rotate) ApplyTo(c vg.Canvas) { c.Rotate(a.Angle) } func (a *Rotate) callerLocation() *callerLocation { return &a.l } // Translate corresponds to the vg.Canvas.Translate method. type Translate struct { Point vg.Point l callerLocation } // Translate implements the Translate method of the vg.Canvas interface. func (c *Canvas) Translate(pt vg.Point) { c.append(&Translate{Point: pt}) } // Call returns the method call that generated the action. func (a *Translate) Call() string { return fmt.Sprintf("%sTranslate(%v, %v)", a.l, a.Point.X, a.Point.Y) } // ApplyTo applies the action to the given vg.Canvas. func (a *Translate) ApplyTo(c vg.Canvas) { c.Translate(a.Point) } func (a *Translate) callerLocation() *callerLocation { return &a.l } // Scale corresponds to the vg.Canvas.Scale method. type Scale struct { X, Y float64 l callerLocation } // Scale implements the Scale method of the vg.Canvas interface. func (c *Canvas) Scale(x, y float64) { c.append(&Scale{X: x, Y: y}) } // Call returns the method call that generated the action. func (a *Scale) Call() string { return fmt.Sprintf("%sScale(%v, %v)", a.l, a.X, a.Y) } // ApplyTo applies the action to the given vg.Canvas. func (a *Scale) ApplyTo(c vg.Canvas) { c.Scale(a.X, a.Y) } func (a *Scale) callerLocation() *callerLocation { return &a.l } // Push corresponds to the vg.Canvas.Push method. type Push struct { l callerLocation } // Push implements the Push method of the vg.Canvas interface. func (c *Canvas) Push() { c.append(&Push{}) } // Call returns the method call that generated the action. func (a *Push) Call() string { return fmt.Sprintf("%sPush()", a.l) } // ApplyTo applies the action to the given vg.Canvas. func (a *Push) ApplyTo(c vg.Canvas) { c.Push() } func (a *Push) callerLocation() *callerLocation { return &a.l } // Pop corresponds to the vg.Canvas.Pop method. type Pop struct { l callerLocation } // Pop implements the Pop method of the vg.Canvas interface. func (c *Canvas) Pop() { c.append(&Pop{}) } // Call returns the method call that generated the action. func (a *Pop) Call() string { return fmt.Sprintf("%sPop()", a.l) } // ApplyTo applies the action to the given vg.Canvas. func (a *Pop) ApplyTo(c vg.Canvas) { c.Pop() } func (a *Pop) callerLocation() *callerLocation { return &a.l } // Stroke corresponds to the vg.Canvas.Stroke method. type Stroke struct { Path vg.Path l callerLocation } // Stroke implements the Stroke method of the vg.Canvas interface. func (c *Canvas) Stroke(path vg.Path) { c.append(&Stroke{Path: append(vg.Path(nil), path...)}) } // Call returns the method call that generated the action. func (a *Stroke) Call() string { return fmt.Sprintf("%sStroke(%#v)", a.l, a.Path) } // ApplyTo applies the action to the given vg.Canvas. func (a *Stroke) ApplyTo(c vg.Canvas) { c.Stroke(a.Path) } func (a *Stroke) callerLocation() *callerLocation { return &a.l } // Fill corresponds to the vg.Canvas.Fill method. type Fill struct { Path vg.Path l callerLocation } // Fill implements the Fill method of the vg.Canvas interface. func (c *Canvas) Fill(path vg.Path) { c.append(&Fill{Path: append(vg.Path(nil), path...)}) } // Call returns the method call that generated the action. func (a *Fill) Call() string { return fmt.Sprintf("%sFill(%#v)", a.l, a.Path) } // ApplyTo applies the action to the given vg.Canvas. func (a *Fill) ApplyTo(c vg.Canvas) { c.Fill(a.Path) } func (a *Fill) callerLocation() *callerLocation { return &a.l } // FillString corresponds to the vg.Canvas.FillString method. type FillString struct { Font font.Font Size vg.Length Point vg.Point String string l callerLocation fonts map[fontID]font.Face } // FillString implements the FillString method of the vg.Canvas interface. func (c *Canvas) FillString(font font.Face, pt vg.Point, str string) { c.append(&FillString{ Font: font.Font, Size: font.Font.Size, Point: pt, String: str, }) } // ApplyTo applies the action to the given vg.Canvas. func (a *FillString) ApplyTo(c vg.Canvas) { c.FillString(a.fonts[fontID{name: a.Font.Name(), size: a.Size}], a.Point, a.String) } // Call returns the pseudo method call that generated the action. func (a *FillString) Call() string { return fmt.Sprintf("%sFillString(%q, %v, %v, %v, %q)", a.l, a.Font.Name(), a.Size, a.Point.X, a.Point.Y, a.String) } func (a *FillString) callerLocation() *callerLocation { return &a.l } // DrawImage corresponds to the vg.Canvas.DrawImage method type DrawImage struct { Rectangle vg.Rectangle Image image.Image l callerLocation } // DrawImage implements the DrawImage method of the vg.Canvas interface. func (c *Canvas) DrawImage(rect vg.Rectangle, img image.Image) { c.append(&DrawImage{ Rectangle: rect, Image: img, }) } // ApplyTo applies the action to the given vg.Canvas. func (a *DrawImage) ApplyTo(c vg.Canvas) { c.DrawImage(a.Rectangle, a.Image) } // Call returns the pseudo method call that generated the action. func (a *DrawImage) Call() string { var buf bytes.Buffer err := png.Encode(&buf, a.Image) if err != nil { panic(fmt.Errorf("recorder: error encoding image to PNG: %v", err)) } b64 := base64.StdEncoding.EncodeToString(buf.Bytes()) return fmt.Sprintf("%sDrawImage(%#v, {%#v, IMAGE:%s})", a.l, a.Rectangle, a.Image.Bounds(), b64) } func (a *DrawImage) callerLocation() *callerLocation { return &a.l } // Commenter defines types that can record comments. type Commenter interface { Comment(string) } var _ Commenter = (*Canvas)(nil) // Comment implements a Recorder comment mechanism. type Comment struct { Text string l callerLocation } // Comment adds a comment to a list of Actions.. func (c *Canvas) Comment(text string) { c.append(&Comment{Text: text}) } // Call returns the method call that generated the action. func (a *Comment) Call() string { return fmt.Sprintf("%sComment(%q)", a.l, a.Text) } // ApplyTo applies the action to the given vg.Canvas. func (a *Comment) ApplyTo(c vg.Canvas) { if c, ok := c.(Commenter); ok { c.Comment(a.Text) } } func (a *Comment) callerLocation() *callerLocation { return &a.l }