Files
sjy01-image-proc/vendor/gonum.org/v1/plot/vg/vgtex/canvas.go
2024-10-24 15:46:01 +08:00

330 lines
7.9 KiB
Go

// Copyright ©2016 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 vgtex provides a vg.Canvas implementation for LaTeX, targeted at
// the TikZ/PGF LaTeX package: https://sourceforge.net/projects/pgf
//
// vgtex generates PGF instructions that will be interpreted and rendered by LaTeX.
// vgtex allows to put any valid LaTeX notation inside plot's strings.
package vgtex // import "gonum.org/v1/plot/vg/vgtex"
import (
"bufio"
"bytes"
"fmt"
"image"
"image/color"
"image/png"
"io"
"math"
"os"
"strings"
"time"
"gonum.org/v1/plot/font"
"gonum.org/v1/plot/vg"
"gonum.org/v1/plot/vg/draw"
)
const degPerRadian = 180 / math.Pi
const (
defaultHeader = `%%%%%% generated by gonum/plot %%%%%%
\documentclass{standalone}
\usepackage{pgf}
\begin{document}
`
defaultFooter = "\\end{document}\n"
)
func init() {
draw.RegisterFormat("tex", func(w, h vg.Length) vg.CanvasWriterTo {
return NewDocument(w, h)
})
}
// Canvas implements the vg.Canvas interface, translating drawing
// primitives from gonum/plot to PGF.
type Canvas struct {
buf *bytes.Buffer
w, h vg.Length
stack []context
// If document is true, Canvas.WriteTo will generate a standalone
// .tex file that can be fed to, e.g., pdflatex.
document bool
id int64 // id is a unique identifier for this canvas
}
type context struct {
color color.Color
dashArray []vg.Length
dashOffset vg.Length
linew vg.Length
}
// New returns a new LaTeX canvas.
func New(w, h vg.Length) *Canvas {
return newCanvas(w, h, false)
}
// NewDocument returns a new LaTeX canvas that can be readily
// compiled into a standalone document.
func NewDocument(w, h vg.Length) *Canvas {
return newCanvas(w, h, true)
}
func newCanvas(w, h vg.Length, document bool) *Canvas {
c := &Canvas{
buf: new(bytes.Buffer),
w: w,
h: h,
document: document,
id: time.Now().UnixNano(),
}
if !document {
c.wtex(`%%%% gonum/plot created for LaTeX/pgf`)
c.wtex(`%%%% you need to add:`)
c.wtex(`%%%% \usepackage{pgf}`)
c.wtex(`%%%% to your LaTeX document`)
}
c.wtex("")
c.wtex(`\begin{pgfpicture}`)
c.stack = make([]context, 1)
vg.Initialize(c)
return c
}
func (c *Canvas) context() *context {
return &c.stack[len(c.stack)-1]
}
// Size returns the width and height of the canvas.
func (c *Canvas) Size() (w, h vg.Length) {
return c.w, c.h
}
// SetLineWidth implements the vg.Canvas.SetLineWidth method.
func (c *Canvas) SetLineWidth(w vg.Length) {
c.context().linew = w
}
// SetLineDash implements the vg.Canvas.SetLineDash method.
func (c *Canvas) SetLineDash(pattern []vg.Length, offset vg.Length) {
c.context().dashArray = pattern
c.context().dashOffset = offset
}
// SetColor implements the vg.Canvas.SetColor method.
func (c *Canvas) SetColor(clr color.Color) {
c.context().color = clr
}
// Rotate implements the vg.Canvas.Rotate method.
func (c *Canvas) Rotate(rad float64) {
c.wtex(`\pgftransformrotate{%g}`, rad*degPerRadian)
}
// Translate implements the vg.Canvas.Translate method.
func (c *Canvas) Translate(pt vg.Point) {
c.wtex(`\pgftransformshift{\pgfpoint{%gpt}{%gpt}}`, pt.X, pt.Y)
}
// Scale implements the vg.Canvas.Scale method.
func (c *Canvas) Scale(x, y float64) {
c.wtex(`\pgftransformxscale{%g}`, x)
c.wtex(`\pgftransformyscale{%g}`, y)
}
// Push implements the vg.Canvas.Push method.
func (c *Canvas) Push() {
c.wtex(`\begin{pgfscope}`)
c.stack = append(c.stack, *c.context())
}
// Pop implements the vg.Canvas.Pop method.
func (c *Canvas) Pop() {
c.stack = c.stack[:len(c.stack)-1]
c.wtex(`\end{pgfscope}`)
c.wtex("")
}
// Stroke implements the vg.Canvas.Stroke method.
func (c *Canvas) Stroke(p vg.Path) {
if c.context().linew <= 0 {
return
}
c.Push()
c.wstyle()
c.wpath(p)
c.wtex(`\pgfusepath{stroke}`)
c.Pop()
}
// Fill implements the vg.Canvas.Fill method.
func (c *Canvas) Fill(p vg.Path) {
c.Push()
c.wstyle()
c.wpath(p)
c.wtex(`\pgfusepath{fill}`)
c.Pop()
}
// FillString implements the vg.Canvas.FillString method.
func (c *Canvas) FillString(f font.Face, pt vg.Point, text string) {
c.Push()
c.wcolor()
pt.X += 0.5 * f.Width(text)
c.wtex(`\pgftext[base,at={\pgfpoint{%gpt}{%gpt}}]{{\fontsize{%gpt}{%gpt}\selectfont %s}}`, pt.X, pt.Y, f.Font.Size, f.Font.Size, text)
c.Pop()
}
// DrawImage implements the vg.Canvas.DrawImage method.
// DrawImage will first save the image inside a PNG file and have the
// generated LaTeX reference that file.
// The file name will be "gonum-pgf-image-<canvas-id>-<time.Now()>.png
func (c *Canvas) DrawImage(rect vg.Rectangle, img image.Image) {
fname := fmt.Sprintf("gonum-pgf-image-%v-%v.png", c.id, time.Now().UnixNano())
f, err := os.Create(fname)
if err != nil {
panic(err)
}
defer f.Close()
err = png.Encode(f, img)
if err != nil {
panic(fmt.Errorf("vgtex: error encoding image to PNG: %v", err))
}
var (
xmin = rect.Min.X
ymin = rect.Min.Y
width = rect.Size().X
height = rect.Size().Y
)
c.wtex(`\pgftext[base,left,at=\pgfpoint{%gpt}{%gpt}]{\pgfimage[height=%gpt,width=%gpt]{%s}}`, xmin, ymin, height, width, fname)
}
func (c *Canvas) indent(s string) string {
return strings.Repeat(s, len(c.stack))
}
func (c *Canvas) wtex(s string, args ...interface{}) {
fmt.Fprintf(c.buf, c.indent(" ")+s+"\n", args...)
}
func (c *Canvas) wstyle() {
c.wdash()
c.wlineWidth()
c.wcolor()
}
func (c *Canvas) wdash() {
if len(c.context().dashArray) == 0 {
c.wtex(`\pgfsetdash{}{0pt}`)
return
}
str := `\pgfsetdash{`
for _, d := range c.context().dashArray {
str += fmt.Sprintf("{%gpt}", d)
}
str += fmt.Sprintf("}{%gpt}", c.context().dashOffset)
c.wtex(str)
}
func (c *Canvas) wlineWidth() {
c.wtex(`\pgfsetlinewidth{%gpt}`, c.context().linew)
}
func (c *Canvas) wcolor() {
col := c.context().color
if col == nil {
col = color.Black
}
r, g, b, a := col.RGBA()
// FIXME(sbinet) \color will last until the end of the current TeX group
// use \pgfsetcolor and \pgfsetstrokecolor instead.
// it needs a named color: define it on the fly (storing it at the beginning
// of the document.)
c.wtex(
`\color[rgb]{%g,%g,%g}`,
float64(r)/math.MaxUint16,
float64(g)/math.MaxUint16,
float64(b)/math.MaxUint16,
)
opacity := float64(a) / math.MaxUint16
c.wtex(`\pgfsetstrokeopacity{%g}`, opacity)
c.wtex(`\pgfsetfillopacity{%g}`, opacity)
}
func (c *Canvas) wpath(p vg.Path) {
for _, comp := range p {
switch comp.Type {
case vg.MoveComp:
c.wtex(`\pgfpathmoveto{\pgfpoint{%gpt}{%gpt}}`, comp.Pos.X, comp.Pos.Y)
case vg.LineComp:
c.wtex(`\pgflineto{\pgfpoint{%gpt}{%gpt}}`, comp.Pos.X, comp.Pos.Y)
case vg.ArcComp:
start := comp.Start * degPerRadian
angle := comp.Angle * degPerRadian
r := comp.Radius
c.wtex(`\pgfpatharc{%g}{%g}{%gpt}`, start, angle, r)
case vg.CurveComp:
var a, b vg.Point
switch len(comp.Control) {
case 1:
a = comp.Control[0]
b = a
case 2:
a = comp.Control[0]
b = comp.Control[1]
default:
panic("vgtex: invalid number of control points")
}
c.wtex(`\pgfcurveto{\pgfpoint{%gpt}{%gpt}}{\pgfpoint{%gpt}{%gpt}}{\pgfpoint{%gpt}{%gpt}}`,
a.X, a.Y, b.X, b.Y, comp.Pos.X, comp.Pos.Y)
case vg.CloseComp:
c.wtex("%% path-close")
default:
panic(fmt.Errorf("vgtex: unknown path component type: %v", comp.Type))
}
}
}
// WriteTo implements the io.WriterTo interface, writing a LaTeX/pgf plot.
func (c *Canvas) WriteTo(w io.Writer) (int64, error) {
var (
n int64
nn int
err error
)
b := bufio.NewWriter(w)
if c.document {
nn, err = b.Write([]byte(defaultHeader))
n += int64(nn)
if err != nil {
return n, err
}
}
m, err := c.buf.WriteTo(b)
n += m
if err != nil {
return n, err
}
nn, err = fmt.Fprintf(b, "\\end{pgfpicture}\n")
n += int64(nn)
if err != nil {
return n, err
}
if c.document {
nn, err = b.Write([]byte(defaultFooter))
n += int64(nn)
if err != nil {
return n, err
}
}
return n, b.Flush()
}