1
2
3
4
5
6
7
8
9
10 package 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
48
49 type Canvas struct {
50 buf *bytes.Buffer
51 w, h vg.Length
52 stack []context
53
54
55
56 document bool
57 id int64
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
68 func New(w, h vg.Length) *Canvas {
69 return newCanvas(w, h, false)
70 }
71
72
73
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
104 func (c *Canvas) Size() (w, h vg.Length) {
105 return c.w, c.h
106 }
107
108
109 func (c *Canvas) SetLineWidth(w vg.Length) {
110 c.context().linew = w
111 }
112
113
114 func (c *Canvas) SetLineDash(pattern []vg.Length, offset vg.Length) {
115 c.context().dashArray = pattern
116 c.context().dashOffset = offset
117 }
118
119
120 func (c *Canvas) SetColor(clr color.Color) {
121 c.context().color = clr
122 }
123
124
125 func (c *Canvas) Rotate(rad float64) {
126 c.wtex(`\pgftransformrotate{%g}`, rad*degPerRadian)
127 }
128
129
130 func (c *Canvas) Translate(pt vg.Point) {
131 c.wtex(`\pgftransformshift{\pgfpoint{%gpt}{%gpt}}`, pt.X, pt.Y)
132 }
133
134
135 func (c *Canvas) Scale(x, y float64) {
136 c.wtex(`\pgftransformxscale{%g}`, x)
137 c.wtex(`\pgftransformyscale{%g}`, y)
138 }
139
140
141 func (c *Canvas) Push() {
142 c.wtex(`\begin{pgfscope}`)
143 c.stack = append(c.stack, *c.context())
144 }
145
146
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
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
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
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
184
185
186
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
246
247
248
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
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