...

Source file src/gonum.org/v1/plot/vg/recorder/recorder.go

Documentation: gonum.org/v1/plot/vg/recorder

     1  // Copyright ©2015 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package recorder provides support for vector graphics serialization.
     6  package recorder // import "gonum.org/v1/plot/vg/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  // Canvas implements vg.Canvas operation serialization.
    25  type Canvas struct {
    26  	// Actions holds a log of all methods called on
    27  	// the canvas.
    28  	Actions []Action
    29  
    30  	// KeepCaller indicates whether the Canvas will
    31  	// retain runtime caller location for the actions.
    32  	// This includes source filename and line number.
    33  	KeepCaller bool
    34  
    35  	// c holds a backing vg.Canvas. If c is non-nil
    36  	// then method calls to the Canvas will be
    37  	// reflected to c.
    38  	c vg.Canvas
    39  
    40  	// fonts holds a collection of font/size descriptions.
    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  // Action is a vector graphics action as defined by the
    51  // vg.Canvas interface. Each method of Canvas has a
    52  // corresponding Action type.
    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  // Reset resets the Canvas to the base state.
    77  func (c *Canvas) Reset() {
    78  	c.Actions = c.Actions[:0]
    79  }
    80  
    81  // ReplayOn applies the set of Actions recorded by the Recorder onto
    82  // the destination Canvas.
    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  // SetLineWidth corresponds to the vg.Canvas.SetWidth method.
   125  type SetLineWidth struct {
   126  	Width vg.Length
   127  
   128  	l callerLocation
   129  }
   130  
   131  // SetLineWidth implements the SetLineWidth method of the vg.Canvas interface.
   132  func (c *Canvas) SetLineWidth(w vg.Length) {
   133  	c.append(&SetLineWidth{Width: w})
   134  }
   135  
   136  // Call returns the method call that generated the action.
   137  func (a *SetLineWidth) Call() string {
   138  	return fmt.Sprintf("%sSetLineWidth(%v)", a.l, a.Width)
   139  }
   140  
   141  // ApplyTo applies the action to the given vg.Canvas.
   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  // SetLineDash corresponds to the vg.Canvas.SetLineDash method.
   151  type SetLineDash struct {
   152  	Dashes  []vg.Length
   153  	Offsets vg.Length
   154  
   155  	l callerLocation
   156  }
   157  
   158  // SetLineDash implements the SetLineDash method of the vg.Canvas interface.
   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  // Call returns the method call that generated the action.
   167  func (a *SetLineDash) Call() string {
   168  	return fmt.Sprintf("%sSetLineDash(%#v, %v)", a.l, a.Dashes, a.Offsets)
   169  }
   170  
   171  // ApplyTo applies the action to the given vg.Canvas.
   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  // SetColor corresponds to the vg.Canvas.SetColor method.
   181  type SetColor struct {
   182  	Color color.Color
   183  
   184  	l callerLocation
   185  }
   186  
   187  // SetColor implements the SetColor method of the vg.Canvas interface.
   188  func (c *Canvas) SetColor(col color.Color) {
   189  	c.append(&SetColor{Color: col})
   190  }
   191  
   192  // Call returns the method call that generated the action.
   193  func (a *SetColor) Call() string {
   194  	return fmt.Sprintf("%sSetColor(%#v)", a.l, a.Color)
   195  }
   196  
   197  // ApplyTo applies the action to the given vg.Canvas.
   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  // Rotate corresponds to the vg.Canvas.Rotate method.
   207  type Rotate struct {
   208  	Angle float64
   209  
   210  	l callerLocation
   211  }
   212  
   213  // Rotate implements the Rotate method of the vg.Canvas interface.
   214  func (c *Canvas) Rotate(a float64) {
   215  	c.append(&Rotate{Angle: a})
   216  }
   217  
   218  // Call returns the method call that generated the action.
   219  func (a *Rotate) Call() string {
   220  	return fmt.Sprintf("%sRotate(%v)", a.l, a.Angle)
   221  }
   222  
   223  // ApplyTo applies the action to the given vg.Canvas.
   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  // Translate corresponds to the vg.Canvas.Translate method.
   233  type Translate struct {
   234  	Point vg.Point
   235  
   236  	l callerLocation
   237  }
   238  
   239  // Translate implements the Translate method of the vg.Canvas interface.
   240  func (c *Canvas) Translate(pt vg.Point) {
   241  	c.append(&Translate{Point: pt})
   242  }
   243  
   244  // Call returns the method call that generated the action.
   245  func (a *Translate) Call() string {
   246  	return fmt.Sprintf("%sTranslate(%v, %v)", a.l, a.Point.X, a.Point.Y)
   247  }
   248  
   249  // ApplyTo applies the action to the given vg.Canvas.
   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  // Scale corresponds to the vg.Canvas.Scale method.
   259  type Scale struct {
   260  	X, Y float64
   261  
   262  	l callerLocation
   263  }
   264  
   265  // Scale implements the Scale method of the vg.Canvas interface.
   266  func (c *Canvas) Scale(x, y float64) {
   267  	c.append(&Scale{X: x, Y: y})
   268  }
   269  
   270  // Call returns the method call that generated the action.
   271  func (a *Scale) Call() string {
   272  	return fmt.Sprintf("%sScale(%v, %v)", a.l, a.X, a.Y)
   273  }
   274  
   275  // ApplyTo applies the action to the given vg.Canvas.
   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  // Push corresponds to the vg.Canvas.Push method.
   285  type Push struct {
   286  	l callerLocation
   287  }
   288  
   289  // Push implements the Push method of the vg.Canvas interface.
   290  func (c *Canvas) Push() {
   291  	c.append(&Push{})
   292  }
   293  
   294  // Call returns the method call that generated the action.
   295  func (a *Push) Call() string {
   296  	return fmt.Sprintf("%sPush()", a.l)
   297  }
   298  
   299  // ApplyTo applies the action to the given vg.Canvas.
   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  // Pop corresponds to the vg.Canvas.Pop method.
   309  type Pop struct {
   310  	l callerLocation
   311  }
   312  
   313  // Pop implements the Pop method of the vg.Canvas interface.
   314  func (c *Canvas) Pop() {
   315  	c.append(&Pop{})
   316  }
   317  
   318  // Call returns the method call that generated the action.
   319  func (a *Pop) Call() string {
   320  	return fmt.Sprintf("%sPop()", a.l)
   321  }
   322  
   323  // ApplyTo applies the action to the given vg.Canvas.
   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  // Stroke corresponds to the vg.Canvas.Stroke method.
   333  type Stroke struct {
   334  	Path vg.Path
   335  
   336  	l callerLocation
   337  }
   338  
   339  // Stroke implements the Stroke method of the vg.Canvas interface.
   340  func (c *Canvas) Stroke(path vg.Path) {
   341  	c.append(&Stroke{Path: append(vg.Path(nil), path...)})
   342  }
   343  
   344  // Call returns the method call that generated the action.
   345  func (a *Stroke) Call() string {
   346  	return fmt.Sprintf("%sStroke(%#v)", a.l, a.Path)
   347  }
   348  
   349  // ApplyTo applies the action to the given vg.Canvas.
   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  // Fill corresponds to the vg.Canvas.Fill method.
   359  type Fill struct {
   360  	Path vg.Path
   361  
   362  	l callerLocation
   363  }
   364  
   365  // Fill implements the Fill method of the vg.Canvas interface.
   366  func (c *Canvas) Fill(path vg.Path) {
   367  	c.append(&Fill{Path: append(vg.Path(nil), path...)})
   368  }
   369  
   370  // Call returns the method call that generated the action.
   371  func (a *Fill) Call() string {
   372  	return fmt.Sprintf("%sFill(%#v)", a.l, a.Path)
   373  }
   374  
   375  // ApplyTo applies the action to the given vg.Canvas.
   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  // FillString corresponds to the vg.Canvas.FillString method.
   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  // FillString implements the FillString method of the vg.Canvas interface.
   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  // ApplyTo applies the action to the given vg.Canvas.
   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  // Call returns the pseudo method call that generated the action.
   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  // DrawImage corresponds to the vg.Canvas.DrawImage method
   421  type DrawImage struct {
   422  	Rectangle vg.Rectangle
   423  	Image     image.Image
   424  
   425  	l callerLocation
   426  }
   427  
   428  // DrawImage implements the DrawImage method of the vg.Canvas interface.
   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  // ApplyTo applies the action to the given vg.Canvas.
   437  func (a *DrawImage) ApplyTo(c vg.Canvas) {
   438  	c.DrawImage(a.Rectangle, a.Image)
   439  }
   440  
   441  // Call returns the pseudo method call that generated the action.
   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  // Commenter defines types that can record comments.
   457  type Commenter interface {
   458  	Comment(string)
   459  }
   460  
   461  var _ Commenter = (*Canvas)(nil)
   462  
   463  // Comment implements a Recorder comment mechanism.
   464  type Comment struct {
   465  	Text string
   466  
   467  	l callerLocation
   468  }
   469  
   470  // Comment adds a comment to a list of Actions..
   471  func (c *Canvas) Comment(text string) {
   472  	c.append(&Comment{Text: text})
   473  }
   474  
   475  // Call returns the method call that generated the action.
   476  func (a *Comment) Call() string {
   477  	return fmt.Sprintf("%sComment(%q)", a.l, a.Text)
   478  }
   479  
   480  // ApplyTo applies the action to the given vg.Canvas.
   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