...

Source file src/gonum.org/v1/plot/vg/vgtex/canvas.go

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

     1  // Copyright ©2016 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 vgtex provides a vg.Canvas implementation for LaTeX, targeted at
     6  // the TikZ/PGF LaTeX package: https://sourceforge.net/projects/pgf
     7  //
     8  // vgtex generates PGF instructions that will be interpreted and rendered by LaTeX.
     9  // vgtex allows to put any valid LaTeX notation inside plot's strings.
    10  package vgtex // import "gonum.org/v1/plot/vg/vgtex"
    11  
    12  import (
    13  	"bufio"
    14  	"bytes"
    15  	"fmt"
    16  	"image"
    17  	"image/color"
    18  	"image/png"
    19  	"io"
    20  	"math"
    21  	"os"
    22  	"strings"
    23  	"time"
    24  
    25  	"gonum.org/v1/plot/font"
    26  	"gonum.org/v1/plot/vg"
    27  	"gonum.org/v1/plot/vg/draw"
    28  )
    29  
    30  const degPerRadian = 180 / math.Pi
    31  
    32  const (
    33  	defaultHeader = `%%%%%% generated by gonum/plot %%%%%%
    34  \documentclass{standalone}
    35  \usepackage{pgf}
    36  \begin{document}
    37  `
    38  	defaultFooter = "\\end{document}\n"
    39  )
    40  
    41  func init() {
    42  	draw.RegisterFormat("tex", func(w, h vg.Length) vg.CanvasWriterTo {
    43  		return NewDocument(w, h)
    44  	})
    45  }
    46  
    47  // Canvas implements the vg.Canvas interface, translating drawing
    48  // primitives from gonum/plot to PGF.
    49  type Canvas struct {
    50  	buf   *bytes.Buffer
    51  	w, h  vg.Length
    52  	stack []context
    53  
    54  	// If document is true, Canvas.WriteTo will generate a standalone
    55  	// .tex file that can be fed to, e.g., pdflatex.
    56  	document bool
    57  	id       int64 // id is a unique identifier for this canvas
    58  }
    59  
    60  type context struct {
    61  	color      color.Color
    62  	dashArray  []vg.Length
    63  	dashOffset vg.Length
    64  	linew      vg.Length
    65  }
    66  
    67  // New returns a new LaTeX canvas.
    68  func New(w, h vg.Length) *Canvas {
    69  	return newCanvas(w, h, false)
    70  }
    71  
    72  // NewDocument returns a new LaTeX canvas that can be readily
    73  // compiled into a standalone document.
    74  func NewDocument(w, h vg.Length) *Canvas {
    75  	return newCanvas(w, h, true)
    76  }
    77  
    78  func newCanvas(w, h vg.Length, document bool) *Canvas {
    79  	c := &Canvas{
    80  		buf:      new(bytes.Buffer),
    81  		w:        w,
    82  		h:        h,
    83  		document: document,
    84  		id:       time.Now().UnixNano(),
    85  	}
    86  	if !document {
    87  		c.wtex(`%%%% gonum/plot created for LaTeX/pgf`)
    88  		c.wtex(`%%%% you need to add:`)
    89  		c.wtex(`%%%%   \usepackage{pgf}`)
    90  		c.wtex(`%%%% to your LaTeX document`)
    91  	}
    92  	c.wtex("")
    93  	c.wtex(`\begin{pgfpicture}`)
    94  	c.stack = make([]context, 1)
    95  	vg.Initialize(c)
    96  	return c
    97  }
    98  
    99  func (c *Canvas) context() *context {
   100  	return &c.stack[len(c.stack)-1]
   101  }
   102  
   103  // Size returns the width and height of the canvas.
   104  func (c *Canvas) Size() (w, h vg.Length) {
   105  	return c.w, c.h
   106  }
   107  
   108  // SetLineWidth implements the vg.Canvas.SetLineWidth method.
   109  func (c *Canvas) SetLineWidth(w vg.Length) {
   110  	c.context().linew = w
   111  }
   112  
   113  // SetLineDash implements the vg.Canvas.SetLineDash method.
   114  func (c *Canvas) SetLineDash(pattern []vg.Length, offset vg.Length) {
   115  	c.context().dashArray = pattern
   116  	c.context().dashOffset = offset
   117  }
   118  
   119  // SetColor implements the vg.Canvas.SetColor method.
   120  func (c *Canvas) SetColor(clr color.Color) {
   121  	c.context().color = clr
   122  }
   123  
   124  // Rotate implements the vg.Canvas.Rotate method.
   125  func (c *Canvas) Rotate(rad float64) {
   126  	c.wtex(`\pgftransformrotate{%g}`, rad*degPerRadian)
   127  }
   128  
   129  // Translate implements the vg.Canvas.Translate method.
   130  func (c *Canvas) Translate(pt vg.Point) {
   131  	c.wtex(`\pgftransformshift{\pgfpoint{%gpt}{%gpt}}`, pt.X, pt.Y)
   132  }
   133  
   134  // Scale implements the vg.Canvas.Scale method.
   135  func (c *Canvas) Scale(x, y float64) {
   136  	c.wtex(`\pgftransformxscale{%g}`, x)
   137  	c.wtex(`\pgftransformyscale{%g}`, y)
   138  }
   139  
   140  // Push implements the vg.Canvas.Push method.
   141  func (c *Canvas) Push() {
   142  	c.wtex(`\begin{pgfscope}`)
   143  	c.stack = append(c.stack, *c.context())
   144  }
   145  
   146  // Pop implements the vg.Canvas.Pop method.
   147  func (c *Canvas) Pop() {
   148  	c.stack = c.stack[:len(c.stack)-1]
   149  	c.wtex(`\end{pgfscope}`)
   150  	c.wtex("")
   151  }
   152  
   153  // Stroke implements the vg.Canvas.Stroke method.
   154  func (c *Canvas) Stroke(p vg.Path) {
   155  	if c.context().linew <= 0 {
   156  		return
   157  	}
   158  	c.Push()
   159  	c.wstyle()
   160  	c.wpath(p)
   161  	c.wtex(`\pgfusepath{stroke}`)
   162  	c.Pop()
   163  }
   164  
   165  // Fill implements the vg.Canvas.Fill method.
   166  func (c *Canvas) Fill(p vg.Path) {
   167  	c.Push()
   168  	c.wstyle()
   169  	c.wpath(p)
   170  	c.wtex(`\pgfusepath{fill}`)
   171  	c.Pop()
   172  }
   173  
   174  // FillString implements the vg.Canvas.FillString method.
   175  func (c *Canvas) FillString(f font.Face, pt vg.Point, text string) {
   176  	c.Push()
   177  	c.wcolor()
   178  	pt.X += 0.5 * f.Width(text)
   179  	c.wtex(`\pgftext[base,at={\pgfpoint{%gpt}{%gpt}}]{{\fontsize{%gpt}{%gpt}\selectfont %s}}`, pt.X, pt.Y, f.Font.Size, f.Font.Size, text)
   180  	c.Pop()
   181  }
   182  
   183  // DrawImage implements the vg.Canvas.DrawImage method.
   184  // DrawImage will first save the image inside a PNG file and have the
   185  // generated LaTeX reference that file.
   186  // The file name will be "gonum-pgf-image-<canvas-id>-<time.Now()>.png
   187  func (c *Canvas) DrawImage(rect vg.Rectangle, img image.Image) {
   188  	fname := fmt.Sprintf("gonum-pgf-image-%v-%v.png", c.id, time.Now().UnixNano())
   189  	f, err := os.Create(fname)
   190  	if err != nil {
   191  		panic(err)
   192  	}
   193  	defer f.Close()
   194  	err = png.Encode(f, img)
   195  	if err != nil {
   196  		panic(fmt.Errorf("vgtex: error encoding image to PNG: %v", err))
   197  	}
   198  
   199  	var (
   200  		xmin   = rect.Min.X
   201  		ymin   = rect.Min.Y
   202  		width  = rect.Size().X
   203  		height = rect.Size().Y
   204  	)
   205  	c.wtex(`\pgftext[base,left,at=\pgfpoint{%gpt}{%gpt}]{\pgfimage[height=%gpt,width=%gpt]{%s}}`, xmin, ymin, height, width, fname)
   206  }
   207  
   208  func (c *Canvas) indent(s string) string {
   209  	return strings.Repeat(s, len(c.stack))
   210  }
   211  
   212  func (c *Canvas) wtex(s string, args ...interface{}) {
   213  	fmt.Fprintf(c.buf, c.indent("  ")+s+"\n", args...)
   214  }
   215  
   216  func (c *Canvas) wstyle() {
   217  	c.wdash()
   218  	c.wlineWidth()
   219  	c.wcolor()
   220  }
   221  
   222  func (c *Canvas) wdash() {
   223  	if len(c.context().dashArray) == 0 {
   224  		c.wtex(`\pgfsetdash{}{0pt}`)
   225  		return
   226  	}
   227  	str := `\pgfsetdash{`
   228  	for _, d := range c.context().dashArray {
   229  		str += fmt.Sprintf("{%gpt}", d)
   230  	}
   231  	str += fmt.Sprintf("}{%gpt}", c.context().dashOffset)
   232  	c.wtex(str)
   233  }
   234  
   235  func (c *Canvas) wlineWidth() {
   236  	c.wtex(`\pgfsetlinewidth{%gpt}`, c.context().linew)
   237  }
   238  
   239  func (c *Canvas) wcolor() {
   240  	col := c.context().color
   241  	if col == nil {
   242  		col = color.Black
   243  	}
   244  	r, g, b, a := col.RGBA()
   245  	// FIXME(sbinet) \color will last until the end of the current TeX group
   246  	// use \pgfsetcolor and \pgfsetstrokecolor instead.
   247  	// it needs a named color: define it on the fly (storing it at the beginning
   248  	// of the document.)
   249  	c.wtex(
   250  		`\color[rgb]{%g,%g,%g}`,
   251  		float64(r)/math.MaxUint16,
   252  		float64(g)/math.MaxUint16,
   253  		float64(b)/math.MaxUint16,
   254  	)
   255  
   256  	opacity := float64(a) / math.MaxUint16
   257  	c.wtex(`\pgfsetstrokeopacity{%g}`, opacity)
   258  	c.wtex(`\pgfsetfillopacity{%g}`, opacity)
   259  }
   260  
   261  func (c *Canvas) wpath(p vg.Path) {
   262  	for _, comp := range p {
   263  		switch comp.Type {
   264  		case vg.MoveComp:
   265  			c.wtex(`\pgfpathmoveto{\pgfpoint{%gpt}{%gpt}}`, comp.Pos.X, comp.Pos.Y)
   266  		case vg.LineComp:
   267  			c.wtex(`\pgflineto{\pgfpoint{%gpt}{%gpt}}`, comp.Pos.X, comp.Pos.Y)
   268  		case vg.ArcComp:
   269  			start := comp.Start * degPerRadian
   270  			angle := comp.Angle * degPerRadian
   271  			r := comp.Radius
   272  			c.wtex(`\pgfpatharc{%g}{%g}{%gpt}`, start, angle, r)
   273  		case vg.CurveComp:
   274  			var a, b vg.Point
   275  			switch len(comp.Control) {
   276  			case 1:
   277  				a = comp.Control[0]
   278  				b = a
   279  			case 2:
   280  				a = comp.Control[0]
   281  				b = comp.Control[1]
   282  			default:
   283  				panic("vgtex: invalid number of control points")
   284  			}
   285  			c.wtex(`\pgfcurveto{\pgfpoint{%gpt}{%gpt}}{\pgfpoint{%gpt}{%gpt}}{\pgfpoint{%gpt}{%gpt}}`,
   286  				a.X, a.Y, b.X, b.Y, comp.Pos.X, comp.Pos.Y)
   287  		case vg.CloseComp:
   288  			c.wtex("%% path-close")
   289  		default:
   290  			panic(fmt.Errorf("vgtex: unknown path component type: %v", comp.Type))
   291  		}
   292  	}
   293  }
   294  
   295  // WriteTo implements the io.WriterTo interface, writing a LaTeX/pgf plot.
   296  func (c *Canvas) WriteTo(w io.Writer) (int64, error) {
   297  	var (
   298  		n   int64
   299  		nn  int
   300  		err error
   301  	)
   302  	b := bufio.NewWriter(w)
   303  	if c.document {
   304  		nn, err = b.Write([]byte(defaultHeader))
   305  		n += int64(nn)
   306  		if err != nil {
   307  			return n, err
   308  		}
   309  	}
   310  	m, err := c.buf.WriteTo(b)
   311  	n += m
   312  	if err != nil {
   313  		return n, err
   314  	}
   315  	nn, err = fmt.Fprintf(b, "\\end{pgfpicture}\n")
   316  	n += int64(nn)
   317  	if err != nil {
   318  		return n, err
   319  	}
   320  
   321  	if c.document {
   322  		nn, err = b.Write([]byte(defaultFooter))
   323  		n += int64(nn)
   324  		if err != nil {
   325  			return n, err
   326  		}
   327  	}
   328  	return n, b.Flush()
   329  }
   330  

View as plain text