fixed dependencies
This commit is contained in:
633
vendor/gonum.org/v1/plot/vg/draw/canvas.go
generated
vendored
Normal file
633
vendor/gonum.org/v1/plot/vg/draw/canvas.go
generated
vendored
Normal file
@@ -0,0 +1,633 @@
|
||||
// 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 draw // import "gonum.org/v1/plot/vg/draw"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"gonum.org/v1/plot/text"
|
||||
"gonum.org/v1/plot/vg"
|
||||
)
|
||||
|
||||
// formats holds the registered canvas image formats
|
||||
var formats = struct {
|
||||
sync.RWMutex
|
||||
m map[string]func(w, h vg.Length) vg.CanvasWriterTo
|
||||
}{
|
||||
m: make(map[string]func(w, h vg.Length) vg.CanvasWriterTo),
|
||||
}
|
||||
|
||||
// Formats returns the sorted list of registered vg formats.
|
||||
func Formats() []string {
|
||||
formats.RLock()
|
||||
defer formats.RUnlock()
|
||||
|
||||
list := make([]string, 0, len(formats.m))
|
||||
for name := range formats.m {
|
||||
list = append(list, name)
|
||||
}
|
||||
sort.Strings(list)
|
||||
return list
|
||||
}
|
||||
|
||||
// RegisterFormat registers an image format for use by NewFormattedCanvas.
|
||||
// name is the name of the format, like "jpeg" or "png".
|
||||
// fn is the construction function to call for the format.
|
||||
//
|
||||
// RegisterFormat panics if fn is nil.
|
||||
func RegisterFormat(name string, fn func(w, h vg.Length) vg.CanvasWriterTo) {
|
||||
formats.Lock()
|
||||
defer formats.Unlock()
|
||||
|
||||
if fn == nil {
|
||||
panic("draw: RegisterFormat with nil function")
|
||||
}
|
||||
formats.m[name] = fn
|
||||
}
|
||||
|
||||
// A Canvas is a vector graphics canvas along with
|
||||
// an associated Rectangle defining a section of the canvas
|
||||
// to which drawing should take place.
|
||||
type Canvas struct {
|
||||
vg.Canvas
|
||||
vg.Rectangle
|
||||
}
|
||||
|
||||
// XAlignment specifies text alignment in the X direction. Three preset
|
||||
// options are available, but an arbitrary alignment
|
||||
// can also be specified using XAlignment(desired number).
|
||||
type XAlignment = text.XAlignment
|
||||
|
||||
const (
|
||||
// XLeft aligns the left edge of the text with the specified location.
|
||||
XLeft = text.XLeft
|
||||
// XCenter aligns the horizontal center of the text with the specified location.
|
||||
XCenter = text.XCenter
|
||||
// XRight aligns the right edge of the text with the specified location.
|
||||
XRight = text.XRight
|
||||
)
|
||||
|
||||
// YAlignment specifies text alignment in the Y direction. Three preset
|
||||
// options are available, but an arbitrary alignment
|
||||
// can also be specified using YAlignment(desired number).
|
||||
type YAlignment = text.YAlignment
|
||||
|
||||
const (
|
||||
// YTop aligns the top of of the text with the specified location.
|
||||
YTop = text.YTop
|
||||
// YCenter aligns the vertical center of the text with the specified location.
|
||||
YCenter = text.YCenter
|
||||
// YBottom aligns the bottom of the text with the specified location.
|
||||
YBottom = text.YBottom
|
||||
)
|
||||
|
||||
// Position specifies the text position.
|
||||
const (
|
||||
PosLeft = text.PosLeft
|
||||
PosBottom = text.PosBottom
|
||||
PosCenter = text.PosCenter
|
||||
PosTop = text.PosTop
|
||||
PosRight = text.PosRight
|
||||
)
|
||||
|
||||
// LineStyle describes what a line will look like.
|
||||
type LineStyle struct {
|
||||
// Color is the color of the line.
|
||||
Color color.Color
|
||||
|
||||
// Width is the width of the line.
|
||||
Width vg.Length
|
||||
|
||||
Dashes []vg.Length
|
||||
DashOffs vg.Length
|
||||
}
|
||||
|
||||
// A GlyphStyle specifies the look of a glyph used to draw
|
||||
// a point on a plot.
|
||||
type GlyphStyle struct {
|
||||
// Color is the color used to draw the glyph.
|
||||
color.Color
|
||||
|
||||
// Radius specifies the size of the glyph's radius.
|
||||
Radius vg.Length
|
||||
|
||||
// Shape draws the shape of the glyph.
|
||||
Shape GlyphDrawer
|
||||
}
|
||||
|
||||
// A GlyphDrawer wraps the DrawGlyph function.
|
||||
type GlyphDrawer interface {
|
||||
// DrawGlyph draws the glyph at the given
|
||||
// point, with the given color and radius.
|
||||
DrawGlyph(*Canvas, GlyphStyle, vg.Point)
|
||||
}
|
||||
|
||||
// DrawGlyph draws the given glyph to the draw
|
||||
// area. If the point is not within the Canvas
|
||||
// or the sty.Shape is nil then nothing is drawn.
|
||||
func (c *Canvas) DrawGlyph(sty GlyphStyle, pt vg.Point) {
|
||||
if sty.Shape == nil || !c.Contains(pt) {
|
||||
return
|
||||
}
|
||||
c.SetColor(sty.Color)
|
||||
sty.Shape.DrawGlyph(c, sty, pt)
|
||||
}
|
||||
|
||||
// DrawGlyphNoClip draws the given glyph to the draw
|
||||
// area. If the sty.Shape is nil then nothing is drawn.
|
||||
func (c *Canvas) DrawGlyphNoClip(sty GlyphStyle, pt vg.Point) {
|
||||
if sty.Shape == nil {
|
||||
return
|
||||
}
|
||||
c.SetColor(sty.Color)
|
||||
sty.Shape.DrawGlyph(c, sty, pt)
|
||||
}
|
||||
|
||||
// Rectangle returns the rectangle surrounding this glyph,
|
||||
// assuming that it is drawn centered at 0,0
|
||||
func (g GlyphStyle) Rectangle() vg.Rectangle {
|
||||
return vg.Rectangle{
|
||||
Min: vg.Point{X: -g.Radius, Y: -g.Radius},
|
||||
Max: vg.Point{X: +g.Radius, Y: +g.Radius},
|
||||
}
|
||||
}
|
||||
|
||||
// CircleGlyph is a glyph that draws a solid circle.
|
||||
type CircleGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the GlyphDrawer interface.
|
||||
func (CircleGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
p := make(vg.Path, 0, 3)
|
||||
p.Move(vg.Point{X: pt.X + sty.Radius, Y: pt.Y})
|
||||
p.Arc(pt, sty.Radius, 0, 2*math.Pi)
|
||||
p.Close()
|
||||
c.Fill(p)
|
||||
}
|
||||
|
||||
// RingGlyph is a glyph that draws the outline of a circle.
|
||||
type RingGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (RingGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
|
||||
p := make(vg.Path, 0, 3)
|
||||
p.Move(vg.Point{X: pt.X + sty.Radius, Y: pt.Y})
|
||||
p.Arc(pt, sty.Radius, 0, 2*math.Pi)
|
||||
p.Close()
|
||||
c.Stroke(p)
|
||||
}
|
||||
|
||||
const (
|
||||
cosπover4 = vg.Length(.707106781202420)
|
||||
sinπover6 = vg.Length(.500000000025921)
|
||||
cosπover6 = vg.Length(.866025403769473)
|
||||
)
|
||||
|
||||
// SquareGlyph is a glyph that draws the outline of a square.
|
||||
type SquareGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (SquareGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
|
||||
x := (sty.Radius-sty.Radius*cosπover4)/2 + sty.Radius*cosπover4
|
||||
p := make(vg.Path, 0, 5)
|
||||
p.Move(vg.Point{X: pt.X - x, Y: pt.Y - x})
|
||||
p.Line(vg.Point{X: pt.X + x, Y: pt.Y - x})
|
||||
p.Line(vg.Point{X: pt.X + x, Y: pt.Y + x})
|
||||
p.Line(vg.Point{X: pt.X - x, Y: pt.Y + x})
|
||||
p.Close()
|
||||
c.Stroke(p)
|
||||
}
|
||||
|
||||
// BoxGlyph is a glyph that draws a filled square.
|
||||
type BoxGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (BoxGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
x := (sty.Radius-sty.Radius*cosπover4)/2 + sty.Radius*cosπover4
|
||||
p := make(vg.Path, 0, 5)
|
||||
p.Move(vg.Point{X: pt.X - x, Y: pt.Y - x})
|
||||
p.Line(vg.Point{X: pt.X + x, Y: pt.Y - x})
|
||||
p.Line(vg.Point{X: pt.X + x, Y: pt.Y + x})
|
||||
p.Line(vg.Point{X: pt.X - x, Y: pt.Y + x})
|
||||
p.Close()
|
||||
c.Fill(p)
|
||||
}
|
||||
|
||||
// TriangleGlyph is a glyph that draws the outline of a triangle.
|
||||
type TriangleGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (TriangleGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
|
||||
r := sty.Radius + (sty.Radius-sty.Radius*sinπover6)/2
|
||||
p := make(vg.Path, 0, 4)
|
||||
p.Move(vg.Point{X: pt.X, Y: pt.Y + r})
|
||||
p.Line(vg.Point{X: pt.X - r*cosπover6, Y: pt.Y - r*sinπover6})
|
||||
p.Line(vg.Point{X: pt.X + r*cosπover6, Y: pt.Y - r*sinπover6})
|
||||
p.Close()
|
||||
c.Stroke(p)
|
||||
}
|
||||
|
||||
// PyramidGlyph is a glyph that draws a filled triangle.
|
||||
type PyramidGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (PyramidGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
r := sty.Radius + (sty.Radius-sty.Radius*sinπover6)/2
|
||||
p := make(vg.Path, 0, 4)
|
||||
p.Move(vg.Point{X: pt.X, Y: pt.Y + r})
|
||||
p.Line(vg.Point{X: pt.X - r*cosπover6, Y: pt.Y - r*sinπover6})
|
||||
p.Line(vg.Point{X: pt.X + r*cosπover6, Y: pt.Y - r*sinπover6})
|
||||
p.Close()
|
||||
c.Fill(p)
|
||||
}
|
||||
|
||||
// PlusGlyph is a glyph that draws a plus sign
|
||||
type PlusGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (PlusGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
|
||||
r := sty.Radius
|
||||
p := make(vg.Path, 0, 2)
|
||||
p.Move(vg.Point{X: pt.X, Y: pt.Y + r})
|
||||
p.Line(vg.Point{X: pt.X, Y: pt.Y - r})
|
||||
c.Stroke(p)
|
||||
p = p[:0]
|
||||
p.Move(vg.Point{X: pt.X - r, Y: pt.Y})
|
||||
p.Line(vg.Point{X: pt.X + r, Y: pt.Y})
|
||||
c.Stroke(p)
|
||||
}
|
||||
|
||||
// CrossGlyph is a glyph that draws a big X.
|
||||
type CrossGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (CrossGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
|
||||
r := sty.Radius * cosπover4
|
||||
p := make(vg.Path, 0, 2)
|
||||
p.Move(vg.Point{X: pt.X - r, Y: pt.Y - r})
|
||||
p.Line(vg.Point{X: pt.X + r, Y: pt.Y + r})
|
||||
c.Stroke(p)
|
||||
p = p[:0]
|
||||
p.Move(vg.Point{X: pt.X - r, Y: pt.Y + r})
|
||||
p.Line(vg.Point{X: pt.X + r, Y: pt.Y - r})
|
||||
c.Stroke(p)
|
||||
}
|
||||
|
||||
// New returns a new (bounded) draw.Canvas.
|
||||
func New(c vg.CanvasSizer) Canvas {
|
||||
w, h := c.Size()
|
||||
return NewCanvas(c, w, h)
|
||||
}
|
||||
|
||||
// NewFormattedCanvas creates a new vg.CanvasWriterTo with the specified
|
||||
// image format. Supported formats need to be registered by importing one or
|
||||
// more of the following packages:
|
||||
//
|
||||
// - gonum.org/v1/plot/vg/vgeps: provides eps
|
||||
// - gonum.org/v1/plot/vg/vgimg: provides png, jpg|jpeg, tif|tiff
|
||||
// - gonum.org/v1/plot/vg/vgpdf: provides pdf
|
||||
// - gonum.org/v1/plot/vg/vgsvg: provides svg
|
||||
// - gonum.org/v1/plot/vg/vgtex: provides tex
|
||||
func NewFormattedCanvas(w, h vg.Length, format string) (vg.CanvasWriterTo, error) {
|
||||
formats.RLock()
|
||||
defer formats.RUnlock()
|
||||
|
||||
for name, fn := range formats.m {
|
||||
if format != name {
|
||||
continue
|
||||
}
|
||||
return fn(w, h), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported format: %q", format)
|
||||
}
|
||||
|
||||
// NewCanvas returns a new (bounded) draw.Canvas of the given size.
|
||||
func NewCanvas(c vg.Canvas, w, h vg.Length) Canvas {
|
||||
return Canvas{
|
||||
Canvas: c,
|
||||
Rectangle: vg.Rectangle{
|
||||
Min: vg.Point{X: 0, Y: 0},
|
||||
Max: vg.Point{X: w, Y: h},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Center returns the center point of the area
|
||||
func (c *Canvas) Center() vg.Point {
|
||||
return vg.Point{
|
||||
X: (c.Max.X-c.Min.X)/2 + c.Min.X,
|
||||
Y: (c.Max.Y-c.Min.Y)/2 + c.Min.Y,
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true if the Canvas contains the point.
|
||||
func (c *Canvas) Contains(p vg.Point) bool {
|
||||
return c.ContainsX(p.X) && c.ContainsY(p.Y)
|
||||
}
|
||||
|
||||
// ContainsX returns true if the Canvas contains the
|
||||
// x coordinate.
|
||||
func (c *Canvas) ContainsX(x vg.Length) bool {
|
||||
return x <= c.Max.X+slop && x >= c.Min.X-slop
|
||||
}
|
||||
|
||||
// ContainsY returns true if the Canvas contains the
|
||||
// y coordinate.
|
||||
func (c *Canvas) ContainsY(y vg.Length) bool {
|
||||
return y <= c.Max.Y+slop && y >= c.Min.Y-slop
|
||||
}
|
||||
|
||||
// X returns the value of x, given in the unit range,
|
||||
// in the drawing coordinates of this draw area.
|
||||
// A value of 0, for example, will return the minimum
|
||||
// x value of the draw area and a value of 1 will
|
||||
// return the maximum.
|
||||
func (c *Canvas) X(x float64) vg.Length {
|
||||
return vg.Length(x)*(c.Max.X-c.Min.X) + c.Min.X
|
||||
}
|
||||
|
||||
// Y returns the value of x, given in the unit range,
|
||||
// in the drawing coordinates of this draw area.
|
||||
// A value of 0, for example, will return the minimum
|
||||
// y value of the draw area and a value of 1 will
|
||||
// return the maximum.
|
||||
func (c *Canvas) Y(y float64) vg.Length {
|
||||
return vg.Length(y)*(c.Max.Y-c.Min.Y) + c.Min.Y
|
||||
}
|
||||
|
||||
// Crop returns a new Canvas corresponding to the Canvas
|
||||
// c with the given lengths added to the minimum
|
||||
// and maximum x and y values of the Canvas's Rectangle.
|
||||
// Note that cropping the right and top sides of the canvas
|
||||
// requires specifying negative values of right and top.
|
||||
func Crop(c Canvas, left, right, bottom, top vg.Length) Canvas {
|
||||
minpt := vg.Point{
|
||||
X: c.Min.X + left,
|
||||
Y: c.Min.Y + bottom,
|
||||
}
|
||||
maxpt := vg.Point{
|
||||
X: c.Max.X + right,
|
||||
Y: c.Max.Y + top,
|
||||
}
|
||||
return Canvas{
|
||||
Canvas: c,
|
||||
Rectangle: vg.Rectangle{Min: minpt, Max: maxpt},
|
||||
}
|
||||
}
|
||||
|
||||
// Tiles creates regular subcanvases from a Canvas.
|
||||
type Tiles struct {
|
||||
// Cols and Rows specify the number of rows and columns of tiles.
|
||||
Cols, Rows int
|
||||
// PadTop, PadBottom, PadRight, and PadLeft specify the padding
|
||||
// on the corresponding side of each tile.
|
||||
PadTop, PadBottom, PadRight, PadLeft vg.Length
|
||||
// PadX and PadY specify the padding between columns and rows
|
||||
// of tiles respectively..
|
||||
PadX, PadY vg.Length
|
||||
}
|
||||
|
||||
// At returns the subcanvas within c that corresponds to the
|
||||
// tile at column x, row y.
|
||||
func (ts Tiles) At(c Canvas, x, y int) Canvas {
|
||||
tileH := (c.Max.Y - c.Min.Y - ts.PadTop - ts.PadBottom -
|
||||
vg.Length(ts.Rows-1)*ts.PadY) / vg.Length(ts.Rows)
|
||||
tileW := (c.Max.X - c.Min.X - ts.PadLeft - ts.PadRight -
|
||||
vg.Length(ts.Cols-1)*ts.PadX) / vg.Length(ts.Cols)
|
||||
|
||||
ymax := c.Max.Y - ts.PadTop - vg.Length(y)*(ts.PadY+tileH)
|
||||
ymin := ymax - tileH
|
||||
xmin := c.Min.X + ts.PadLeft + vg.Length(x)*(ts.PadX+tileW)
|
||||
xmax := xmin + tileW
|
||||
|
||||
return Canvas{
|
||||
Canvas: vg.Canvas(c),
|
||||
Rectangle: vg.Rectangle{
|
||||
Min: vg.Point{X: xmin, Y: ymin},
|
||||
Max: vg.Point{X: xmax, Y: ymax},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SetLineStyle sets the current line style
|
||||
func (c *Canvas) SetLineStyle(sty LineStyle) {
|
||||
c.SetColor(sty.Color)
|
||||
c.SetLineWidth(sty.Width)
|
||||
c.SetLineDash(sty.Dashes, sty.DashOffs)
|
||||
}
|
||||
|
||||
// StrokeLines draws a line connecting a set of points
|
||||
// in the given Canvas.
|
||||
func (c *Canvas) StrokeLines(sty LineStyle, lines ...[]vg.Point) {
|
||||
if len(lines) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c.SetLineStyle(sty)
|
||||
|
||||
for _, l := range lines {
|
||||
if len(l) == 0 {
|
||||
continue
|
||||
}
|
||||
p := make(vg.Path, 0, len(l))
|
||||
p.Move(l[0])
|
||||
for _, pt := range l[1:] {
|
||||
p.Line(pt)
|
||||
}
|
||||
c.Stroke(p)
|
||||
}
|
||||
}
|
||||
|
||||
// StrokeLine2 draws a line between two points in the given
|
||||
// Canvas.
|
||||
func (c *Canvas) StrokeLine2(sty LineStyle, x0, y0, x1, y1 vg.Length) {
|
||||
c.StrokeLines(sty, []vg.Point{{X: x0, Y: y0}, {X: x1, Y: y1}})
|
||||
}
|
||||
|
||||
// ClipLinesXY returns a slice of lines that
|
||||
// represent the given line clipped in both
|
||||
// X and Y directions.
|
||||
func (c *Canvas) ClipLinesXY(lines ...[]vg.Point) [][]vg.Point {
|
||||
return c.ClipLinesY(c.ClipLinesX(lines...)...)
|
||||
}
|
||||
|
||||
// ClipLinesX returns a slice of lines that
|
||||
// represent the given line clipped in the
|
||||
// X direction.
|
||||
func (c *Canvas) ClipLinesX(lines ...[]vg.Point) (clipped [][]vg.Point) {
|
||||
lines1 := make([][]vg.Point, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
ls := clipLine(isLeft, vg.Point{X: c.Max.X, Y: c.Min.Y}, vg.Point{X: -1, Y: 0}, line)
|
||||
lines1 = append(lines1, ls...)
|
||||
}
|
||||
clipped = make([][]vg.Point, 0, len(lines1))
|
||||
for _, line := range lines1 {
|
||||
ls := clipLine(isRight, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 1, Y: 0}, line)
|
||||
clipped = append(clipped, ls...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ClipLinesY returns a slice of lines that
|
||||
// represent the given line clipped in the
|
||||
// Y direction.
|
||||
func (c *Canvas) ClipLinesY(lines ...[]vg.Point) (clipped [][]vg.Point) {
|
||||
lines1 := make([][]vg.Point, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
ls := clipLine(isAbove, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 0, Y: -1}, line)
|
||||
lines1 = append(lines1, ls...)
|
||||
}
|
||||
clipped = make([][]vg.Point, 0, len(lines1))
|
||||
for _, line := range lines1 {
|
||||
ls := clipLine(isBelow, vg.Point{X: c.Min.X, Y: c.Max.Y}, vg.Point{X: 0, Y: 1}, line)
|
||||
clipped = append(clipped, ls...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// clipLine performs clipping of a line by a single
|
||||
// clipping line specified by the norm, clip point,
|
||||
// and in function.
|
||||
func clipLine(in func(vg.Point, vg.Point) bool, clip, norm vg.Point, pts []vg.Point) (lines [][]vg.Point) {
|
||||
l := make([]vg.Point, 0, len(pts))
|
||||
for i := 1; i < len(pts); i++ {
|
||||
cur, next := pts[i-1], pts[i]
|
||||
curIn, nextIn := in(cur, clip), in(next, clip)
|
||||
switch {
|
||||
case curIn && nextIn:
|
||||
l = append(l, cur)
|
||||
|
||||
case curIn && !nextIn:
|
||||
l = append(l, cur, isect(cur, next, clip, norm))
|
||||
lines = append(lines, l)
|
||||
l = []vg.Point{}
|
||||
|
||||
case !curIn && !nextIn:
|
||||
// do nothing
|
||||
|
||||
default: // !curIn && nextIn
|
||||
l = append(l, isect(cur, next, clip, norm))
|
||||
}
|
||||
if nextIn && i == len(pts)-1 {
|
||||
l = append(l, next)
|
||||
}
|
||||
}
|
||||
if len(l) > 1 {
|
||||
lines = append(lines, l)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FillPolygon fills a polygon with the given color.
|
||||
func (c *Canvas) FillPolygon(clr color.Color, pts []vg.Point) {
|
||||
if len(pts) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c.SetColor(clr)
|
||||
p := make(vg.Path, 0, len(pts)+1)
|
||||
p.Move(pts[0])
|
||||
for _, pt := range pts[1:] {
|
||||
p.Line(pt)
|
||||
}
|
||||
p.Close()
|
||||
c.Fill(p)
|
||||
}
|
||||
|
||||
// ClipPolygonXY returns a slice of lines that
|
||||
// represent the given polygon clipped in both
|
||||
// X and Y directions.
|
||||
func (c *Canvas) ClipPolygonXY(pts []vg.Point) []vg.Point {
|
||||
return c.ClipPolygonY(c.ClipPolygonX(pts))
|
||||
}
|
||||
|
||||
// ClipPolygonX returns a slice of lines that
|
||||
// represent the given polygon clipped in the
|
||||
// X direction.
|
||||
func (c *Canvas) ClipPolygonX(pts []vg.Point) []vg.Point {
|
||||
return clipPoly(isLeft, vg.Point{X: c.Max.X, Y: c.Min.Y}, vg.Point{X: -1, Y: 0},
|
||||
clipPoly(isRight, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 1, Y: 0}, pts))
|
||||
}
|
||||
|
||||
// ClipPolygonY returns a slice of lines that
|
||||
// represent the given polygon clipped in the
|
||||
// Y direction.
|
||||
func (c *Canvas) ClipPolygonY(pts []vg.Point) []vg.Point {
|
||||
return clipPoly(isBelow, vg.Point{X: c.Min.X, Y: c.Max.Y}, vg.Point{X: 0, Y: 1},
|
||||
clipPoly(isAbove, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 0, Y: -1}, pts))
|
||||
}
|
||||
|
||||
// clipPoly performs clipping of a polygon by a single
|
||||
// clipping line specified by the norm, clip point,
|
||||
// and in function.
|
||||
func clipPoly(in func(vg.Point, vg.Point) bool, clip, norm vg.Point, pts []vg.Point) (clipped []vg.Point) {
|
||||
clipped = make([]vg.Point, 0, len(pts))
|
||||
for i := 0; i < len(pts); i++ {
|
||||
j := i + 1
|
||||
if i == len(pts)-1 {
|
||||
j = 0
|
||||
}
|
||||
cur, next := pts[i], pts[j]
|
||||
curIn, nextIn := in(cur, clip), in(next, clip)
|
||||
switch {
|
||||
case curIn && nextIn:
|
||||
clipped = append(clipped, cur)
|
||||
|
||||
case curIn && !nextIn:
|
||||
clipped = append(clipped, cur, isect(cur, next, clip, norm))
|
||||
|
||||
case !curIn && !nextIn:
|
||||
// do nothing
|
||||
|
||||
default: // !curIn && nextIn
|
||||
clipped = append(clipped, isect(cur, next, clip, norm))
|
||||
}
|
||||
}
|
||||
n := len(clipped)
|
||||
return clipped[:n:n]
|
||||
}
|
||||
|
||||
// slop is some slop for floating point equality
|
||||
const slop = 3e-8 // ≈ √1⁻¹⁵
|
||||
|
||||
func isLeft(p, clip vg.Point) bool {
|
||||
return p.X <= clip.X+slop
|
||||
}
|
||||
|
||||
func isRight(p, clip vg.Point) bool {
|
||||
return p.X >= clip.X-slop
|
||||
}
|
||||
|
||||
func isBelow(p, clip vg.Point) bool {
|
||||
return p.Y <= clip.Y+slop
|
||||
}
|
||||
|
||||
func isAbove(p, clip vg.Point) bool {
|
||||
return p.Y >= clip.Y-slop
|
||||
}
|
||||
|
||||
// isect returns the intersection of a line p0→p1 with the
|
||||
// clipping line specified by the clip point and normal.
|
||||
func isect(p0, p1, clip, norm vg.Point) vg.Point {
|
||||
// t = (norm · (p0 - clip)) / (norm · (p0 - p1))
|
||||
t := p0.Sub(clip).Dot(norm) / p0.Sub(p1).Dot(norm)
|
||||
|
||||
// p = p0 + t*(p1 - p0)
|
||||
return p1.Sub(p0).Scale(t).Add(p0)
|
||||
}
|
||||
|
||||
// FillText fills lines of text in the draw area.
|
||||
// pt specifies the location where the text is to be drawn.
|
||||
func (c *Canvas) FillText(sty TextStyle, pt vg.Point, txt string) {
|
||||
sty.Handler.Draw(c, txt, sty, pt)
|
||||
}
|
||||
6
vendor/gonum.org/v1/plot/vg/draw/doc.go
generated
vendored
Normal file
6
vendor/gonum.org/v1/plot/vg/draw/doc.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright ©2021 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 draw provides types and functions to draw shapes on a vg.Canvas.
|
||||
package draw
|
||||
12
vendor/gonum.org/v1/plot/vg/draw/text.go
generated
vendored
Normal file
12
vendor/gonum.org/v1/plot/vg/draw/text.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright ©2020 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 draw // import "gonum.org/v1/plot/vg/draw"
|
||||
|
||||
import (
|
||||
"gonum.org/v1/plot/text"
|
||||
)
|
||||
|
||||
type TextHandler = text.Handler
|
||||
type TextStyle = text.Style
|
||||
12
vendor/gonum.org/v1/plot/vg/draw/text_plain.go
generated
vendored
Normal file
12
vendor/gonum.org/v1/plot/vg/draw/text_plain.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright ©2020 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 draw // import "gonum.org/v1/plot/vg/draw"
|
||||
|
||||
import "gonum.org/v1/plot/text"
|
||||
|
||||
// PlainTextHandler is a text/plain handler.
|
||||
type PlainTextHandler = text.Plain
|
||||
|
||||
var _ text.Handler = (*PlainTextHandler)(nil)
|
||||
66
vendor/gonum.org/v1/plot/vg/geom.go
generated
vendored
Normal file
66
vendor/gonum.org/v1/plot/vg/geom.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// 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 vg
|
||||
|
||||
// A Point is a location in 2d space.
|
||||
//
|
||||
// Points are used for drawing, not for data. For
|
||||
// data, see the XYer interface.
|
||||
type Point struct {
|
||||
X, Y Length
|
||||
}
|
||||
|
||||
// Dot returns the dot product of two points.
|
||||
func (p Point) Dot(q Point) Length {
|
||||
return p.X*q.X + p.Y*q.Y
|
||||
}
|
||||
|
||||
// Add returns the component-wise sum of two points.
|
||||
func (p Point) Add(q Point) Point {
|
||||
return Point{p.X + q.X, p.Y + q.Y}
|
||||
}
|
||||
|
||||
// Sub returns the component-wise difference of two points.
|
||||
func (p Point) Sub(q Point) Point {
|
||||
return Point{p.X - q.X, p.Y - q.Y}
|
||||
}
|
||||
|
||||
// Scale returns the component-wise product of a point and a scalar.
|
||||
func (p Point) Scale(s Length) Point {
|
||||
return Point{p.X * s, p.Y * s}
|
||||
}
|
||||
|
||||
// A Rectangle represents a rectangular region of 2d space.
|
||||
type Rectangle struct {
|
||||
Min Point
|
||||
Max Point
|
||||
}
|
||||
|
||||
// Size returns the width and height of a Rectangle.
|
||||
func (r Rectangle) Size() Point {
|
||||
return Point{
|
||||
X: r.Max.X - r.Min.X,
|
||||
Y: r.Max.Y - r.Min.Y,
|
||||
}
|
||||
}
|
||||
|
||||
// Add returns the rectangle r translated by p.
|
||||
func (r Rectangle) Add(p Point) Rectangle {
|
||||
return Rectangle{
|
||||
Min: r.Min.Add(p),
|
||||
Max: r.Max.Add(p),
|
||||
}
|
||||
}
|
||||
|
||||
// Path returns the path of a Rect specified by its
|
||||
// upper left corner, width and height.
|
||||
func (r Rectangle) Path() (p Path) {
|
||||
p.Move(r.Min)
|
||||
p.Line(Point{X: r.Max.X, Y: r.Min.Y})
|
||||
p.Line(r.Max)
|
||||
p.Line(Point{X: r.Min.X, Y: r.Max.Y})
|
||||
p.Close()
|
||||
return
|
||||
}
|
||||
37
vendor/gonum.org/v1/plot/vg/len.go
generated
vendored
Normal file
37
vendor/gonum.org/v1/plot/vg/len.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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 vg
|
||||
|
||||
import "gonum.org/v1/plot/font"
|
||||
|
||||
// A Length is a unit-independent representation of length.
|
||||
// Internally, the length is stored in postscript points.
|
||||
type Length = font.Length
|
||||
|
||||
// Points returns a length for the given number of points.
|
||||
func Points(pt float64) Length {
|
||||
return font.Points(pt)
|
||||
}
|
||||
|
||||
// Common lengths.
|
||||
const (
|
||||
Inch = font.Inch
|
||||
Centimeter = font.Centimeter
|
||||
Millimeter = font.Millimeter
|
||||
)
|
||||
|
||||
// ParseLength parses a Length string.
|
||||
// A Length string is a possible signed floating number with a unit.
|
||||
// e.g. "42cm" "2.4in" "66pt"
|
||||
// If no unit was given, ParseLength assumes it was (postscript) points.
|
||||
// Currently valid units are:
|
||||
//
|
||||
// - mm (millimeter)
|
||||
// - cm (centimeter)
|
||||
// - in (inch)
|
||||
// - pt (point)
|
||||
func ParseLength(value string) (Length, error) {
|
||||
return font.ParseLength(value)
|
||||
}
|
||||
129
vendor/gonum.org/v1/plot/vg/tee.go
generated
vendored
Normal file
129
vendor/gonum.org/v1/plot/vg/tee.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright ©2020 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 vg
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
)
|
||||
|
||||
// MultiCanvas creates a canvas that duplicates its drawing operations to all
|
||||
// the provided canvases, similar to the Unix tee(1) command.
|
||||
//
|
||||
// Each drawing operation is sent to each listed canvas, one at a time.
|
||||
func MultiCanvas(cs ...Canvas) Canvas {
|
||||
return teeCanvas{cs}
|
||||
}
|
||||
|
||||
type teeCanvas struct {
|
||||
cs []Canvas
|
||||
}
|
||||
|
||||
// SetLineWidth sets the width of stroked paths.
|
||||
// If the width is not positive then stroked lines
|
||||
// are not drawn.
|
||||
func (tee teeCanvas) SetLineWidth(w Length) {
|
||||
for _, c := range tee.cs {
|
||||
c.SetLineWidth(w)
|
||||
}
|
||||
}
|
||||
|
||||
// SetLineDash sets the dash pattern for lines.
|
||||
// The pattern slice specifies the lengths of
|
||||
// alternating dashes and gaps, and the offset
|
||||
// specifies the distance into the dash pattern
|
||||
// to start the dash.
|
||||
func (tee teeCanvas) SetLineDash(pattern []Length, offset Length) {
|
||||
for _, c := range tee.cs {
|
||||
c.SetLineDash(pattern, offset)
|
||||
}
|
||||
}
|
||||
|
||||
// SetColor sets the current drawing color.
|
||||
// Note that fill color and stroke color are
|
||||
// the same, so if you want different fill
|
||||
// and stroke colors then you must set a color,
|
||||
// draw fills, set a new color and then draw lines.
|
||||
func (tee teeCanvas) SetColor(c color.Color) {
|
||||
for _, canvas := range tee.cs {
|
||||
canvas.SetColor(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate applies a rotation transform to the context.
|
||||
// The parameter is specified in radians.
|
||||
func (tee teeCanvas) Rotate(rad float64) {
|
||||
for _, c := range tee.cs {
|
||||
c.Rotate(rad)
|
||||
}
|
||||
}
|
||||
|
||||
// Translate applies a translational transform to the context.
|
||||
func (tee teeCanvas) Translate(pt Point) {
|
||||
for _, c := range tee.cs {
|
||||
c.Translate(pt)
|
||||
}
|
||||
}
|
||||
|
||||
// Scale applies a scaling transform to the context.
|
||||
func (tee teeCanvas) Scale(x, y float64) {
|
||||
for _, c := range tee.cs {
|
||||
c.Scale(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
// Push saves the current line width, the
|
||||
// current dash pattern, the current
|
||||
// transforms, and the current color
|
||||
// onto a stack so that the state can later
|
||||
// be restored by calling Pop().
|
||||
func (tee teeCanvas) Push() {
|
||||
for _, c := range tee.cs {
|
||||
c.Push()
|
||||
}
|
||||
}
|
||||
|
||||
// Pop restores the context saved by the
|
||||
// corresponding call to Push().
|
||||
func (tee teeCanvas) Pop() {
|
||||
for _, c := range tee.cs {
|
||||
c.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
// Stroke strokes the given path.
|
||||
func (tee teeCanvas) Stroke(p Path) {
|
||||
for _, c := range tee.cs {
|
||||
c.Stroke(p)
|
||||
}
|
||||
}
|
||||
|
||||
// Fill fills the given path.
|
||||
func (tee teeCanvas) Fill(p Path) {
|
||||
for _, c := range tee.cs {
|
||||
c.Fill(p)
|
||||
}
|
||||
}
|
||||
|
||||
// FillString fills in text at the specified
|
||||
// location using the given font.
|
||||
// If the font size is zero, the text is not drawn.
|
||||
func (tee teeCanvas) FillString(f font.Face, pt Point, text string) {
|
||||
for _, c := range tee.cs {
|
||||
c.FillString(f, pt, text)
|
||||
}
|
||||
}
|
||||
|
||||
// DrawImage draws the image, scaled to fit
|
||||
// the destination rectangle.
|
||||
func (tee teeCanvas) DrawImage(rect Rectangle, img image.Image) {
|
||||
for _, c := range tee.cs {
|
||||
c.DrawImage(rect, img)
|
||||
}
|
||||
}
|
||||
|
||||
var _ Canvas = (*teeCanvas)(nil)
|
||||
189
vendor/gonum.org/v1/plot/vg/vg.go
generated
vendored
Normal file
189
vendor/gonum.org/v1/plot/vg/vg.go
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
// 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 vg defines an interface for drawing 2D vector graphics.
|
||||
// This package is designed with the hope that many different
|
||||
// vector graphics back-ends can conform to the interface.
|
||||
package vg // import "gonum.org/v1/plot/vg"
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"io"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
)
|
||||
|
||||
// A Canvas is the main drawing interface for 2D vector
|
||||
// graphics.
|
||||
// The origin is in the bottom left corner.
|
||||
type Canvas interface {
|
||||
// SetLineWidth sets the width of stroked paths.
|
||||
// If the width is not positive then stroked lines
|
||||
// are not drawn.
|
||||
//
|
||||
// The initial line width is 1 point.
|
||||
SetLineWidth(Length)
|
||||
|
||||
// SetLineDash sets the dash pattern for lines.
|
||||
// The pattern slice specifies the lengths of
|
||||
// alternating dashes and gaps, and the offset
|
||||
// specifies the distance into the dash pattern
|
||||
// to start the dash.
|
||||
//
|
||||
// The initial dash pattern is a solid line.
|
||||
SetLineDash(pattern []Length, offset Length)
|
||||
|
||||
// SetColor sets the current drawing color.
|
||||
// Note that fill color and stroke color are
|
||||
// the same, so if you want different fill
|
||||
// and stroke colors then you must set a color,
|
||||
// draw fills, set a new color and then draw lines.
|
||||
//
|
||||
// The initial color is black.
|
||||
// If SetColor is called with a nil color then black is used.
|
||||
SetColor(color.Color)
|
||||
|
||||
// Rotate applies a rotation transform to the context.
|
||||
// The parameter is specified in radians.
|
||||
Rotate(rad float64)
|
||||
|
||||
// Translate applies a translational transform
|
||||
// to the context.
|
||||
Translate(pt Point)
|
||||
|
||||
// Scale applies a scaling transform to the
|
||||
// context.
|
||||
Scale(x, y float64)
|
||||
|
||||
// Push saves the current line width, the
|
||||
// current dash pattern, the current
|
||||
// transforms, and the current color
|
||||
// onto a stack so that the state can later
|
||||
// be restored by calling Pop().
|
||||
Push()
|
||||
|
||||
// Pop restores the context saved by the
|
||||
// corresponding call to Push().
|
||||
Pop()
|
||||
|
||||
// Stroke strokes the given path.
|
||||
Stroke(Path)
|
||||
|
||||
// Fill fills the given path.
|
||||
Fill(Path)
|
||||
|
||||
// FillString fills in text at the specified
|
||||
// location using the given font.
|
||||
// If the font size is zero, the text is not drawn.
|
||||
FillString(f font.Face, pt Point, text string)
|
||||
|
||||
// DrawImage draws the image, scaled to fit
|
||||
// the destination rectangle.
|
||||
DrawImage(rect Rectangle, img image.Image)
|
||||
}
|
||||
|
||||
// CanvasSizer is a Canvas with a defined size.
|
||||
type CanvasSizer interface {
|
||||
Canvas
|
||||
Size() (x, y Length)
|
||||
}
|
||||
|
||||
// CanvasWriterTo is a CanvasSizer with a WriteTo method.
|
||||
type CanvasWriterTo interface {
|
||||
CanvasSizer
|
||||
io.WriterTo
|
||||
}
|
||||
|
||||
// Initialize sets all of the canvas's values to their
|
||||
// initial values.
|
||||
func Initialize(c Canvas) {
|
||||
c.SetLineWidth(Points(1))
|
||||
c.SetLineDash([]Length{}, 0)
|
||||
c.SetColor(color.Black)
|
||||
}
|
||||
|
||||
type Path []PathComp
|
||||
|
||||
// Move moves the current location of the path to
|
||||
// the given point.
|
||||
func (p *Path) Move(pt Point) {
|
||||
*p = append(*p, PathComp{Type: MoveComp, Pos: pt})
|
||||
}
|
||||
|
||||
// Line draws a line from the current point to the
|
||||
// given point.
|
||||
func (p *Path) Line(pt Point) {
|
||||
*p = append(*p, PathComp{Type: LineComp, Pos: pt})
|
||||
}
|
||||
|
||||
// Arc adds an arc to the path defined by the center
|
||||
// point of the arc's circle, the radius of the circle
|
||||
// and the start and sweep angles.
|
||||
func (p *Path) Arc(pt Point, rad Length, s, a float64) {
|
||||
*p = append(*p, PathComp{
|
||||
Type: ArcComp,
|
||||
Pos: pt,
|
||||
Radius: rad,
|
||||
Start: s,
|
||||
Angle: a,
|
||||
})
|
||||
}
|
||||
|
||||
// QuadTo adds a quadratic curve element to the path,
|
||||
// given by the control point p1 and end point pt.
|
||||
func (p *Path) QuadTo(p1, pt Point) {
|
||||
*p = append(*p, PathComp{Type: CurveComp, Pos: pt, Control: []Point{p1}})
|
||||
}
|
||||
|
||||
// CubeTo adds a cubic curve element to the path,
|
||||
// given by the control points p1 and p2 and the end point pt.
|
||||
func (p *Path) CubeTo(p1, p2, pt Point) {
|
||||
*p = append(*p, PathComp{Type: CurveComp, Pos: pt, Control: []Point{p1, p2}})
|
||||
}
|
||||
|
||||
// Close closes the path by connecting the current
|
||||
// location to the start location with a line.
|
||||
func (p *Path) Close() {
|
||||
*p = append(*p, PathComp{Type: CloseComp})
|
||||
}
|
||||
|
||||
// Constants that tag the type of each path
|
||||
// component.
|
||||
const (
|
||||
MoveComp = iota
|
||||
LineComp
|
||||
ArcComp
|
||||
CurveComp
|
||||
CloseComp
|
||||
)
|
||||
|
||||
// A PathComp is a component of a path structure.
|
||||
type PathComp struct {
|
||||
// Type is the type of a particluar component.
|
||||
// Based on the type, each of the following
|
||||
// fields may have a different meaning or may
|
||||
// be meaningless.
|
||||
Type int
|
||||
|
||||
// The Pos field is used as the destination
|
||||
// of a MoveComp or LineComp and is the center
|
||||
// point of an ArcComp. It is not used in
|
||||
// the CloseComp.
|
||||
Pos Point
|
||||
|
||||
// Control is one or two intermediate points
|
||||
// for a CurveComp used by QuadTo and CubeTo.
|
||||
Control []Point
|
||||
|
||||
// Radius is only used for ArcComps, it is
|
||||
// the radius of the circle defining the arc.
|
||||
Radius Length
|
||||
|
||||
// Start and Angle are only used for ArcComps.
|
||||
// They define the start angle and sweep angle of
|
||||
// the arc around the circle. The units of the
|
||||
// angle are radians.
|
||||
Start, Angle float64
|
||||
}
|
||||
232
vendor/gonum.org/v1/plot/vg/vgeps/vgeps.go
generated
vendored
Normal file
232
vendor/gonum.org/v1/plot/vg/vgeps/vgeps.go
generated
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
// 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 vgeps implements the vg.Canvas interface using
|
||||
// encapsulated postscript.
|
||||
package vgeps // import "gonum.org/v1/plot/vg/vgeps"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"io"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
func init() {
|
||||
draw.RegisterFormat("eps", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return New(w, h)
|
||||
})
|
||||
}
|
||||
|
||||
// DPI is the nominal resolution of drawing in EPS.
|
||||
const DPI = 72
|
||||
|
||||
type Canvas struct {
|
||||
stack []context
|
||||
w, h vg.Length
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
type context struct {
|
||||
color color.Color
|
||||
width vg.Length
|
||||
dashes []vg.Length
|
||||
offs vg.Length
|
||||
font string
|
||||
fsize vg.Length
|
||||
}
|
||||
|
||||
// pr is the amount of precision to use when outputting float64s.
|
||||
const pr = 5
|
||||
|
||||
// New returns a new Canvas.
|
||||
func New(w, h vg.Length) *Canvas {
|
||||
return NewTitle(w, h, "")
|
||||
}
|
||||
|
||||
// NewTitle returns a new Canvas with the given title string.
|
||||
func NewTitle(w, h vg.Length, title string) *Canvas {
|
||||
c := &Canvas{
|
||||
stack: []context{{}},
|
||||
w: w,
|
||||
h: h,
|
||||
buf: new(bytes.Buffer),
|
||||
}
|
||||
c.buf.WriteString("%%!PS-Adobe-3.0 EPSF-3.0\n")
|
||||
c.buf.WriteString("%%Creator gonum.org/v1/plot/vg/vgeps\n")
|
||||
c.buf.WriteString("%%Title: " + title + "\n")
|
||||
c.buf.WriteString(fmt.Sprintf("%%%%BoundingBox: 0 0 %.*g %.*g\n",
|
||||
pr, w.Dots(DPI),
|
||||
pr, h.Dots(DPI)))
|
||||
c.buf.WriteString(fmt.Sprintf("%%%%CreationDate: %s\n", time.Now()))
|
||||
c.buf.WriteString("%%Orientation: Portrait\n")
|
||||
c.buf.WriteString("%%EndComments\n")
|
||||
c.buf.WriteString("\n")
|
||||
vg.Initialize(c)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Canvas) Size() (w, h vg.Length) {
|
||||
return c.w, c.h
|
||||
}
|
||||
|
||||
// context returns the top context on the stack.
|
||||
func (e *Canvas) context() *context {
|
||||
return &e.stack[len(e.stack)-1]
|
||||
}
|
||||
|
||||
func (e *Canvas) SetLineWidth(w vg.Length) {
|
||||
if e.context().width != w {
|
||||
e.context().width = w
|
||||
fmt.Fprintf(e.buf, "%.*g setlinewidth\n", pr, w.Dots(DPI))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Canvas) SetLineDash(dashes []vg.Length, o vg.Length) {
|
||||
cur := e.context().dashes
|
||||
dashEq := len(dashes) == len(cur)
|
||||
for i := 0; dashEq && i < len(dashes); i++ {
|
||||
if dashes[i] != cur[i] {
|
||||
dashEq = false
|
||||
}
|
||||
}
|
||||
if !dashEq || e.context().offs != o {
|
||||
e.context().dashes = dashes
|
||||
e.context().offs = o
|
||||
e.buf.WriteString("[")
|
||||
for _, d := range dashes {
|
||||
fmt.Fprintf(e.buf, " %.*g", pr, d.Dots(DPI))
|
||||
}
|
||||
e.buf.WriteString(" ] ")
|
||||
fmt.Fprintf(e.buf, "%.*g setdash\n", pr, o.Dots(DPI))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Canvas) SetColor(c color.Color) {
|
||||
if c == nil {
|
||||
c = color.Black
|
||||
}
|
||||
if e.context().color != c {
|
||||
e.context().color = c
|
||||
r, g, b, _ := c.RGBA()
|
||||
mx := float64(math.MaxUint16)
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g %.*g setrgbcolor\n", pr, float64(r)/mx,
|
||||
pr, float64(g)/mx, pr, float64(b)/mx)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Canvas) Rotate(r float64) {
|
||||
fmt.Fprintf(e.buf, "%.*g rotate\n", pr, r*180/math.Pi)
|
||||
}
|
||||
|
||||
func (e *Canvas) Translate(pt vg.Point) {
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g translate\n",
|
||||
pr, pt.X.Dots(DPI), pr, pt.Y.Dots(DPI))
|
||||
}
|
||||
|
||||
func (e *Canvas) Scale(x, y float64) {
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g scale\n", pr, x, pr, y)
|
||||
}
|
||||
|
||||
func (e *Canvas) Push() {
|
||||
e.stack = append(e.stack, *e.context())
|
||||
e.buf.WriteString("gsave\n")
|
||||
}
|
||||
|
||||
func (e *Canvas) Pop() {
|
||||
e.stack = e.stack[:len(e.stack)-1]
|
||||
e.buf.WriteString("grestore\n")
|
||||
}
|
||||
|
||||
func (e *Canvas) Stroke(path vg.Path) {
|
||||
if e.context().width <= 0 {
|
||||
return
|
||||
}
|
||||
e.trace(path)
|
||||
e.buf.WriteString("stroke\n")
|
||||
}
|
||||
|
||||
func (e *Canvas) Fill(path vg.Path) {
|
||||
e.trace(path)
|
||||
e.buf.WriteString("fill\n")
|
||||
}
|
||||
|
||||
func (e *Canvas) trace(path vg.Path) {
|
||||
e.buf.WriteString("newpath\n")
|
||||
for _, comp := range path {
|
||||
switch comp.Type {
|
||||
case vg.MoveComp:
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g moveto\n", pr, comp.Pos.X, pr, comp.Pos.Y)
|
||||
case vg.LineComp:
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g lineto\n", pr, comp.Pos.X, pr, comp.Pos.Y)
|
||||
case vg.ArcComp:
|
||||
end := comp.Start + comp.Angle
|
||||
arcOp := "arc"
|
||||
if comp.Angle < 0 {
|
||||
arcOp = "arcn"
|
||||
}
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g %.*g %.*g %.*g %s\n", pr, comp.Pos.X, pr, comp.Pos.Y,
|
||||
pr, comp.Radius, pr, comp.Start*180/math.Pi, pr,
|
||||
end*180/math.Pi, arcOp)
|
||||
case vg.CurveComp:
|
||||
var p1, p2 vg.Point
|
||||
switch len(comp.Control) {
|
||||
case 1:
|
||||
p1 = comp.Control[0]
|
||||
p2 = p1
|
||||
case 2:
|
||||
p1 = comp.Control[0]
|
||||
p2 = comp.Control[1]
|
||||
default:
|
||||
panic("vgeps: invalid number of control points")
|
||||
}
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g %.*g %.*g %.*g %.*g curveto\n",
|
||||
pr, p1.X, pr, p1.Y, pr, p2.X, pr, p2.Y, pr, comp.Pos.X, pr, comp.Pos.Y)
|
||||
case vg.CloseComp:
|
||||
e.buf.WriteString("closepath\n")
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown path component type: %d\n", comp.Type))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Canvas) FillString(fnt font.Face, pt vg.Point, str string) {
|
||||
if e.context().font != fnt.Name() || e.context().fsize != fnt.Font.Size {
|
||||
e.context().font = fnt.Name()
|
||||
e.context().fsize = fnt.Font.Size
|
||||
fmt.Fprintf(e.buf, "/%s findfont %.*g scalefont setfont\n",
|
||||
fnt.Name(), pr, fnt.Font.Size)
|
||||
}
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g moveto\n", pr, pt.X.Dots(DPI), pr, pt.Y.Dots(DPI))
|
||||
fmt.Fprintf(e.buf, "(%s) show\n", str)
|
||||
}
|
||||
|
||||
// DrawImage implements the vg.Canvas.DrawImage method.
|
||||
func (c *Canvas) DrawImage(rect vg.Rectangle, img image.Image) {
|
||||
// FIXME: https://github.com/gonum/plot/issues/271
|
||||
panic("vgeps: DrawImage not implemented")
|
||||
}
|
||||
|
||||
// WriteTo writes the canvas to an io.Writer.
|
||||
func (e *Canvas) WriteTo(w io.Writer) (int64, error) {
|
||||
b := bufio.NewWriter(w)
|
||||
n, err := e.buf.WriteTo(b)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
m, err := fmt.Fprintln(b, "showpage")
|
||||
n += int64(m)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, b.Flush()
|
||||
}
|
||||
423
vendor/gonum.org/v1/plot/vg/vgimg/vgimg.go
generated
vendored
Normal file
423
vendor/gonum.org/v1/plot/vg/vgimg/vgimg.go
generated
vendored
Normal file
@@ -0,0 +1,423 @@
|
||||
// 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 vgimg implements the vg.Canvas interface using
|
||||
// git.sr.ht/~sbinet/gg as a backend to output raster images.
|
||||
package vgimg // import "gonum.org/v1/plot/vg/vgimg"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
|
||||
"git.sr.ht/~sbinet/gg"
|
||||
"golang.org/x/image/tiff"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/vg"
|
||||
vgdraw "gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
func init() {
|
||||
vgdraw.RegisterFormat("png", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return PngCanvas{Canvas: New(w, h)}
|
||||
})
|
||||
|
||||
vgdraw.RegisterFormat("jpg", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return JpegCanvas{Canvas: New(w, h)}
|
||||
})
|
||||
|
||||
vgdraw.RegisterFormat("jpeg", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return JpegCanvas{Canvas: New(w, h)}
|
||||
})
|
||||
|
||||
vgdraw.RegisterFormat("tif", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return TiffCanvas{Canvas: New(w, h)}
|
||||
})
|
||||
|
||||
vgdraw.RegisterFormat("tiff", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return TiffCanvas{Canvas: New(w, h)}
|
||||
})
|
||||
}
|
||||
|
||||
// Canvas implements the vg.Canvas interface,
|
||||
// drawing to an image.Image using draw2d.
|
||||
type Canvas struct {
|
||||
ctx *gg.Context
|
||||
img draw.Image
|
||||
w, h vg.Length
|
||||
color []color.Color
|
||||
|
||||
// dpi is the number of dots per inch for this canvas.
|
||||
dpi int
|
||||
|
||||
// width is the current line width.
|
||||
width vg.Length
|
||||
|
||||
// backgroundColor is the background color, set by
|
||||
// UseBackgroundColor.
|
||||
backgroundColor color.Color
|
||||
}
|
||||
|
||||
const (
|
||||
// DefaultDPI is the default dot resolution for image
|
||||
// drawing in dots per inch.
|
||||
DefaultDPI = 96
|
||||
|
||||
// DefaultWidth and DefaultHeight are the default canvas
|
||||
// dimensions.
|
||||
DefaultWidth = 4 * vg.Inch
|
||||
DefaultHeight = 4 * vg.Inch
|
||||
)
|
||||
|
||||
// New returns a new image canvas.
|
||||
func New(w, h vg.Length) *Canvas {
|
||||
return NewWith(UseWH(w, h), UseBackgroundColor(color.White))
|
||||
}
|
||||
|
||||
// NewWith returns a new image canvas created according to the specified
|
||||
// options. The currently accepted options are UseWH,
|
||||
// UseDPI, UseImage, and UseImageWithContext.
|
||||
// Each of the options specifies the size of the canvas (UseWH, UseImage),
|
||||
// the resolution of the canvas (UseDPI), or both (useImageWithContext).
|
||||
// If size or resolution are not specified, defaults are used.
|
||||
// It panics if size and resolution are overspecified (i.e., too many options are
|
||||
// passed).
|
||||
func NewWith(o ...option) *Canvas {
|
||||
c := new(Canvas)
|
||||
c.backgroundColor = color.White
|
||||
var g uint32
|
||||
for _, opt := range o {
|
||||
f := opt(c)
|
||||
if g&f != 0 {
|
||||
panic("incompatible options")
|
||||
}
|
||||
g |= f
|
||||
}
|
||||
if c.dpi == 0 {
|
||||
c.dpi = DefaultDPI
|
||||
}
|
||||
if c.w == 0 { // h should also == 0.
|
||||
if c.img == nil {
|
||||
c.w = DefaultWidth
|
||||
c.h = DefaultHeight
|
||||
} else {
|
||||
w := float64(c.img.Bounds().Max.X - c.img.Bounds().Min.X)
|
||||
h := float64(c.img.Bounds().Max.Y - c.img.Bounds().Min.Y)
|
||||
c.w = vg.Length(w/float64(c.dpi)) * vg.Inch
|
||||
c.h = vg.Length(h/float64(c.dpi)) * vg.Inch
|
||||
}
|
||||
}
|
||||
if c.img == nil {
|
||||
w := c.w / vg.Inch * vg.Length(c.dpi)
|
||||
h := c.h / vg.Inch * vg.Length(c.dpi)
|
||||
c.img = draw.Image(image.NewRGBA(image.Rect(0, 0, int(w+0.5), int(h+0.5))))
|
||||
}
|
||||
if c.ctx == nil {
|
||||
c.ctx = gg.NewContextForImage(c.img)
|
||||
c.ctx.SetLineCapButt()
|
||||
c.img = c.ctx.Image().(draw.Image)
|
||||
c.ctx.InvertY()
|
||||
}
|
||||
draw.Draw(c.img, c.img.Bounds(), &image.Uniform{c.backgroundColor}, image.Point{}, draw.Src)
|
||||
c.color = []color.Color{color.Black}
|
||||
vg.Initialize(c)
|
||||
return c
|
||||
}
|
||||
|
||||
// These constants are used to ensure that the options
|
||||
// used when initializing a canvas are compatible with
|
||||
// each other.
|
||||
const (
|
||||
setsDPI uint32 = 1 << iota
|
||||
setsSize
|
||||
setsBackground
|
||||
)
|
||||
|
||||
type option func(*Canvas) uint32
|
||||
|
||||
// UseWH specifies the width and height of the canvas.
|
||||
// The size is rounded up to the nearest pixel.
|
||||
func UseWH(w, h vg.Length) option {
|
||||
return func(c *Canvas) uint32 {
|
||||
if w <= 0 || h <= 0 {
|
||||
panic("w and h must both be > 0.")
|
||||
}
|
||||
c.w, c.h = w, h
|
||||
return setsSize
|
||||
}
|
||||
}
|
||||
|
||||
// UseDPI sets the dots per inch of a canvas. It should only be
|
||||
// used as an option argument when initializing a new canvas.
|
||||
func UseDPI(dpi int) option {
|
||||
if dpi <= 0 {
|
||||
panic("DPI must be > 0.")
|
||||
}
|
||||
return func(c *Canvas) uint32 {
|
||||
c.dpi = dpi
|
||||
return setsDPI
|
||||
}
|
||||
}
|
||||
|
||||
// UseImage specifies an image to create
|
||||
// the canvas from. The
|
||||
// minimum point of the given image
|
||||
// should probably be 0,0.
|
||||
//
|
||||
// Note that a copy of the input image is performed.
|
||||
// This means that modifications applied to the canvas are not reflected
|
||||
// on the original image.
|
||||
func UseImage(img draw.Image) option {
|
||||
return func(c *Canvas) uint32 {
|
||||
c.img = img
|
||||
return setsSize | setsBackground
|
||||
}
|
||||
}
|
||||
|
||||
// UseImageWithContext specifies both an image
|
||||
// and a graphic context to create the canvas from.
|
||||
// The minimum point of the given image
|
||||
// should probably be 0,0.
|
||||
func UseImageWithContext(img draw.Image, ctx *gg.Context) option {
|
||||
return func(c *Canvas) uint32 {
|
||||
c.img = img
|
||||
c.ctx = ctx
|
||||
return setsSize | setsBackground
|
||||
}
|
||||
}
|
||||
|
||||
// UseBackgroundColor specifies the image background color.
|
||||
// Without UseBackgroundColor, the default color is white.
|
||||
func UseBackgroundColor(c color.Color) option {
|
||||
return func(canvas *Canvas) uint32 {
|
||||
canvas.backgroundColor = c
|
||||
return setsBackground
|
||||
}
|
||||
}
|
||||
|
||||
// Image returns the image the canvas is drawing to.
|
||||
//
|
||||
// The dimensions of the returned image must not be modified.
|
||||
func (c *Canvas) Image() draw.Image {
|
||||
return c.img
|
||||
}
|
||||
|
||||
func (c *Canvas) Size() (w, h vg.Length) {
|
||||
return c.w, c.h
|
||||
}
|
||||
|
||||
func (c *Canvas) SetLineWidth(w vg.Length) {
|
||||
c.width = w
|
||||
c.ctx.SetLineWidth(w.Dots(c.DPI()))
|
||||
}
|
||||
|
||||
func (c *Canvas) SetLineDash(ds []vg.Length, offs vg.Length) {
|
||||
dashes := make([]float64, len(ds))
|
||||
for i, d := range ds {
|
||||
dashes[i] = d.Dots(c.DPI())
|
||||
}
|
||||
c.ctx.SetDashOffset(offs.Dots(c.DPI()))
|
||||
c.ctx.SetDash(dashes...)
|
||||
}
|
||||
|
||||
func (c *Canvas) SetColor(clr color.Color) {
|
||||
if clr == nil {
|
||||
clr = color.Black
|
||||
}
|
||||
c.ctx.SetColor(clr)
|
||||
c.color[len(c.color)-1] = clr
|
||||
}
|
||||
|
||||
func (c *Canvas) Rotate(t float64) {
|
||||
c.ctx.Rotate(t)
|
||||
}
|
||||
|
||||
func (c *Canvas) Translate(pt vg.Point) {
|
||||
c.ctx.Translate(pt.X.Dots(c.DPI()), pt.Y.Dots(c.DPI()))
|
||||
}
|
||||
|
||||
func (c *Canvas) Scale(x, y float64) {
|
||||
c.ctx.Scale(x, y)
|
||||
}
|
||||
|
||||
func (c *Canvas) Push() {
|
||||
c.color = append(c.color, c.color[len(c.color)-1])
|
||||
c.ctx.Push()
|
||||
}
|
||||
|
||||
func (c *Canvas) Pop() {
|
||||
c.color = c.color[:len(c.color)-1]
|
||||
c.ctx.Pop()
|
||||
}
|
||||
|
||||
func (c *Canvas) Stroke(p vg.Path) {
|
||||
if c.width <= 0 {
|
||||
return
|
||||
}
|
||||
c.outline(p)
|
||||
c.ctx.Stroke()
|
||||
}
|
||||
|
||||
func (c *Canvas) Fill(p vg.Path) {
|
||||
c.outline(p)
|
||||
c.ctx.Fill()
|
||||
}
|
||||
|
||||
func (c *Canvas) outline(p vg.Path) {
|
||||
for _, comp := range p {
|
||||
switch comp.Type {
|
||||
case vg.MoveComp:
|
||||
c.ctx.MoveTo(comp.Pos.X.Dots(c.DPI()), comp.Pos.Y.Dots(c.DPI()))
|
||||
|
||||
case vg.LineComp:
|
||||
c.ctx.LineTo(comp.Pos.X.Dots(c.DPI()), comp.Pos.Y.Dots(c.DPI()))
|
||||
|
||||
case vg.ArcComp:
|
||||
c.ctx.DrawArc(comp.Pos.X.Dots(c.DPI()), comp.Pos.Y.Dots(c.DPI()),
|
||||
comp.Radius.Dots(c.DPI()),
|
||||
comp.Start, comp.Start+comp.Angle,
|
||||
)
|
||||
|
||||
case vg.CurveComp:
|
||||
switch len(comp.Control) {
|
||||
case 1:
|
||||
c.ctx.QuadraticTo(
|
||||
comp.Control[0].X.Dots(c.DPI()), comp.Control[0].Y.Dots(c.DPI()),
|
||||
comp.Pos.X.Dots(c.DPI()), comp.Pos.Y.Dots(c.DPI()),
|
||||
)
|
||||
case 2:
|
||||
c.ctx.CubicTo(
|
||||
comp.Control[0].X.Dots(c.DPI()), comp.Control[0].Y.Dots(c.DPI()),
|
||||
comp.Control[1].X.Dots(c.DPI()), comp.Control[1].Y.Dots(c.DPI()),
|
||||
comp.Pos.X.Dots(c.DPI()), comp.Pos.Y.Dots(c.DPI()),
|
||||
)
|
||||
default:
|
||||
panic("vgimg: invalid number of control points")
|
||||
}
|
||||
|
||||
case vg.CloseComp:
|
||||
c.ctx.ClosePath()
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown path component: %d", comp.Type))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DPI returns the resolution of the receiver in pixels per inch.
|
||||
func (c *Canvas) DPI() float64 {
|
||||
return float64(c.dpi)
|
||||
}
|
||||
|
||||
func (c *Canvas) FillString(font font.Face, pt vg.Point, str string) {
|
||||
if font.Font.Size == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c.ctx.Push()
|
||||
defer c.ctx.Pop()
|
||||
|
||||
face := font.FontFace(c.DPI())
|
||||
defer face.Close()
|
||||
|
||||
c.ctx.SetFontFace(face)
|
||||
|
||||
x := pt.X.Dots(c.DPI())
|
||||
y := pt.Y.Dots(c.DPI())
|
||||
h := c.h.Dots(c.DPI())
|
||||
|
||||
c.ctx.InvertY()
|
||||
c.ctx.DrawString(str, x, h-y)
|
||||
}
|
||||
|
||||
// DrawImage implements the vg.Canvas.DrawImage method.
|
||||
func (c *Canvas) DrawImage(rect vg.Rectangle, img image.Image) {
|
||||
var (
|
||||
dpi = c.DPI()
|
||||
min = rect.Min
|
||||
xmin = min.X.Dots(dpi)
|
||||
ymin = min.Y.Dots(dpi)
|
||||
rsz = rect.Size()
|
||||
width = rsz.X.Dots(dpi)
|
||||
height = rsz.Y.Dots(dpi)
|
||||
dx = float64(img.Bounds().Dx())
|
||||
dy = float64(img.Bounds().Dy())
|
||||
)
|
||||
c.ctx.Push()
|
||||
c.ctx.Scale(1, -1)
|
||||
c.ctx.Translate(xmin, -ymin-height)
|
||||
c.ctx.Scale(width/dx, height/dy)
|
||||
c.ctx.DrawImage(img, 0, 0)
|
||||
c.ctx.Pop()
|
||||
}
|
||||
|
||||
// WriterCounter implements the io.Writer interface, and counts
|
||||
// the total number of bytes written.
|
||||
type writerCounter struct {
|
||||
io.Writer
|
||||
n int64
|
||||
}
|
||||
|
||||
func (w *writerCounter) Write(p []byte) (int, error) {
|
||||
n, err := w.Writer.Write(p)
|
||||
w.n += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// A JpegCanvas is an image canvas with a WriteTo method
|
||||
// that writes a jpeg image.
|
||||
type JpegCanvas struct {
|
||||
*Canvas
|
||||
}
|
||||
|
||||
// WriteTo implements the io.WriterTo interface, writing a jpeg image.
|
||||
func (c JpegCanvas) WriteTo(w io.Writer) (int64, error) {
|
||||
wc := writerCounter{Writer: w}
|
||||
b := bufio.NewWriter(&wc)
|
||||
if err := jpeg.Encode(b, c.img, nil); err != nil {
|
||||
return wc.n, err
|
||||
}
|
||||
err := b.Flush()
|
||||
return wc.n, err
|
||||
}
|
||||
|
||||
// A PngCanvas is an image canvas with a WriteTo method that
|
||||
// writes a png image.
|
||||
type PngCanvas struct {
|
||||
*Canvas
|
||||
}
|
||||
|
||||
// WriteTo implements the io.WriterTo interface, writing a png image.
|
||||
func (c PngCanvas) WriteTo(w io.Writer) (int64, error) {
|
||||
wc := writerCounter{Writer: w}
|
||||
b := bufio.NewWriter(&wc)
|
||||
if err := png.Encode(b, c.img); err != nil {
|
||||
return wc.n, err
|
||||
}
|
||||
err := b.Flush()
|
||||
return wc.n, err
|
||||
}
|
||||
|
||||
// A TiffCanvas is an image canvas with a WriteTo method that
|
||||
// writes a tiff image.
|
||||
type TiffCanvas struct {
|
||||
*Canvas
|
||||
}
|
||||
|
||||
// WriteTo implements the io.WriterTo interface, writing a tiff image.
|
||||
func (c TiffCanvas) WriteTo(w io.Writer) (int64, error) {
|
||||
wc := writerCounter{Writer: w}
|
||||
b := bufio.NewWriter(&wc)
|
||||
if err := tiff.Encode(b, c.img, nil); err != nil {
|
||||
return wc.n, err
|
||||
}
|
||||
err := b.Flush()
|
||||
return wc.n, err
|
||||
}
|
||||
251
vendor/gonum.org/v1/plot/vg/vgpdf/cp1252.map
generated
vendored
Normal file
251
vendor/gonum.org/v1/plot/vg/vgpdf/cp1252.map
generated
vendored
Normal file
@@ -0,0 +1,251 @@
|
||||
!00 U+0000 .notdef
|
||||
!01 U+0001 .notdef
|
||||
!02 U+0002 .notdef
|
||||
!03 U+0003 .notdef
|
||||
!04 U+0004 .notdef
|
||||
!05 U+0005 .notdef
|
||||
!06 U+0006 .notdef
|
||||
!07 U+0007 .notdef
|
||||
!08 U+0008 .notdef
|
||||
!09 U+0009 .notdef
|
||||
!0A U+000A .notdef
|
||||
!0B U+000B .notdef
|
||||
!0C U+000C .notdef
|
||||
!0D U+000D .notdef
|
||||
!0E U+000E .notdef
|
||||
!0F U+000F .notdef
|
||||
!10 U+0010 .notdef
|
||||
!11 U+0011 .notdef
|
||||
!12 U+0012 .notdef
|
||||
!13 U+0013 .notdef
|
||||
!14 U+0014 .notdef
|
||||
!15 U+0015 .notdef
|
||||
!16 U+0016 .notdef
|
||||
!17 U+0017 .notdef
|
||||
!18 U+0018 .notdef
|
||||
!19 U+0019 .notdef
|
||||
!1A U+001A .notdef
|
||||
!1B U+001B .notdef
|
||||
!1C U+001C .notdef
|
||||
!1D U+001D .notdef
|
||||
!1E U+001E .notdef
|
||||
!1F U+001F .notdef
|
||||
!20 U+0020 space
|
||||
!21 U+0021 exclam
|
||||
!22 U+0022 quotedbl
|
||||
!23 U+0023 numbersign
|
||||
!24 U+0024 dollar
|
||||
!25 U+0025 percent
|
||||
!26 U+0026 ampersand
|
||||
!27 U+0027 quotesingle
|
||||
!28 U+0028 parenleft
|
||||
!29 U+0029 parenright
|
||||
!2A U+002A asterisk
|
||||
!2B U+002B plus
|
||||
!2C U+002C comma
|
||||
!2D U+002D hyphen
|
||||
!2E U+002E period
|
||||
!2F U+002F slash
|
||||
!30 U+0030 zero
|
||||
!31 U+0031 one
|
||||
!32 U+0032 two
|
||||
!33 U+0033 three
|
||||
!34 U+0034 four
|
||||
!35 U+0035 five
|
||||
!36 U+0036 six
|
||||
!37 U+0037 seven
|
||||
!38 U+0038 eight
|
||||
!39 U+0039 nine
|
||||
!3A U+003A colon
|
||||
!3B U+003B semicolon
|
||||
!3C U+003C less
|
||||
!3D U+003D equal
|
||||
!3E U+003E greater
|
||||
!3F U+003F question
|
||||
!40 U+0040 at
|
||||
!41 U+0041 A
|
||||
!42 U+0042 B
|
||||
!43 U+0043 C
|
||||
!44 U+0044 D
|
||||
!45 U+0045 E
|
||||
!46 U+0046 F
|
||||
!47 U+0047 G
|
||||
!48 U+0048 H
|
||||
!49 U+0049 I
|
||||
!4A U+004A J
|
||||
!4B U+004B K
|
||||
!4C U+004C L
|
||||
!4D U+004D M
|
||||
!4E U+004E N
|
||||
!4F U+004F O
|
||||
!50 U+0050 P
|
||||
!51 U+0051 Q
|
||||
!52 U+0052 R
|
||||
!53 U+0053 S
|
||||
!54 U+0054 T
|
||||
!55 U+0055 U
|
||||
!56 U+0056 V
|
||||
!57 U+0057 W
|
||||
!58 U+0058 X
|
||||
!59 U+0059 Y
|
||||
!5A U+005A Z
|
||||
!5B U+005B bracketleft
|
||||
!5C U+005C backslash
|
||||
!5D U+005D bracketright
|
||||
!5E U+005E asciicircum
|
||||
!5F U+005F underscore
|
||||
!60 U+0060 grave
|
||||
!61 U+0061 a
|
||||
!62 U+0062 b
|
||||
!63 U+0063 c
|
||||
!64 U+0064 d
|
||||
!65 U+0065 e
|
||||
!66 U+0066 f
|
||||
!67 U+0067 g
|
||||
!68 U+0068 h
|
||||
!69 U+0069 i
|
||||
!6A U+006A j
|
||||
!6B U+006B k
|
||||
!6C U+006C l
|
||||
!6D U+006D m
|
||||
!6E U+006E n
|
||||
!6F U+006F o
|
||||
!70 U+0070 p
|
||||
!71 U+0071 q
|
||||
!72 U+0072 r
|
||||
!73 U+0073 s
|
||||
!74 U+0074 t
|
||||
!75 U+0075 u
|
||||
!76 U+0076 v
|
||||
!77 U+0077 w
|
||||
!78 U+0078 x
|
||||
!79 U+0079 y
|
||||
!7A U+007A z
|
||||
!7B U+007B braceleft
|
||||
!7C U+007C bar
|
||||
!7D U+007D braceright
|
||||
!7E U+007E asciitilde
|
||||
!7F U+007F .notdef
|
||||
!80 U+20AC Euro
|
||||
!82 U+201A quotesinglbase
|
||||
!83 U+0192 florin
|
||||
!84 U+201E quotedblbase
|
||||
!85 U+2026 ellipsis
|
||||
!86 U+2020 dagger
|
||||
!87 U+2021 daggerdbl
|
||||
!88 U+02C6 circumflex
|
||||
!89 U+2030 perthousand
|
||||
!8A U+0160 Scaron
|
||||
!8B U+2039 guilsinglleft
|
||||
!8C U+0152 OE
|
||||
!8E U+017D Zcaron
|
||||
!91 U+2018 quoteleft
|
||||
!92 U+2019 quoteright
|
||||
!93 U+201C quotedblleft
|
||||
!94 U+201D quotedblright
|
||||
!95 U+2022 bullet
|
||||
!96 U+2013 endash
|
||||
!97 U+2014 emdash
|
||||
!98 U+02DC tilde
|
||||
!99 U+2122 trademark
|
||||
!9A U+0161 scaron
|
||||
!9B U+203A guilsinglright
|
||||
!9C U+0153 oe
|
||||
!9E U+017E zcaron
|
||||
!9F U+0178 Ydieresis
|
||||
!A0 U+00A0 space
|
||||
!A1 U+00A1 exclamdown
|
||||
!A2 U+00A2 cent
|
||||
!A3 U+00A3 sterling
|
||||
!A4 U+00A4 currency
|
||||
!A5 U+00A5 yen
|
||||
!A6 U+00A6 brokenbar
|
||||
!A7 U+00A7 section
|
||||
!A8 U+00A8 dieresis
|
||||
!A9 U+00A9 copyright
|
||||
!AA U+00AA ordfeminine
|
||||
!AB U+00AB guillemotleft
|
||||
!AC U+00AC logicalnot
|
||||
!AD U+00AD hyphen
|
||||
!AE U+00AE registered
|
||||
!AF U+00AF macron
|
||||
!B0 U+00B0 degree
|
||||
!B1 U+00B1 plusminus
|
||||
!B2 U+00B2 twosuperior
|
||||
!B3 U+00B3 threesuperior
|
||||
!B4 U+00B4 acute
|
||||
!B5 U+00B5 mu
|
||||
!B6 U+00B6 paragraph
|
||||
!B7 U+00B7 periodcentered
|
||||
!B8 U+00B8 cedilla
|
||||
!B9 U+00B9 onesuperior
|
||||
!BA U+00BA ordmasculine
|
||||
!BB U+00BB guillemotright
|
||||
!BC U+00BC onequarter
|
||||
!BD U+00BD onehalf
|
||||
!BE U+00BE threequarters
|
||||
!BF U+00BF questiondown
|
||||
!C0 U+00C0 Agrave
|
||||
!C1 U+00C1 Aacute
|
||||
!C2 U+00C2 Acircumflex
|
||||
!C3 U+00C3 Atilde
|
||||
!C4 U+00C4 Adieresis
|
||||
!C5 U+00C5 Aring
|
||||
!C6 U+00C6 AE
|
||||
!C7 U+00C7 Ccedilla
|
||||
!C8 U+00C8 Egrave
|
||||
!C9 U+00C9 Eacute
|
||||
!CA U+00CA Ecircumflex
|
||||
!CB U+00CB Edieresis
|
||||
!CC U+00CC Igrave
|
||||
!CD U+00CD Iacute
|
||||
!CE U+00CE Icircumflex
|
||||
!CF U+00CF Idieresis
|
||||
!D0 U+00D0 Eth
|
||||
!D1 U+00D1 Ntilde
|
||||
!D2 U+00D2 Ograve
|
||||
!D3 U+00D3 Oacute
|
||||
!D4 U+00D4 Ocircumflex
|
||||
!D5 U+00D5 Otilde
|
||||
!D6 U+00D6 Odieresis
|
||||
!D7 U+00D7 multiply
|
||||
!D8 U+00D8 Oslash
|
||||
!D9 U+00D9 Ugrave
|
||||
!DA U+00DA Uacute
|
||||
!DB U+00DB Ucircumflex
|
||||
!DC U+00DC Udieresis
|
||||
!DD U+00DD Yacute
|
||||
!DE U+00DE Thorn
|
||||
!DF U+00DF germandbls
|
||||
!E0 U+00E0 agrave
|
||||
!E1 U+00E1 aacute
|
||||
!E2 U+00E2 acircumflex
|
||||
!E3 U+00E3 atilde
|
||||
!E4 U+00E4 adieresis
|
||||
!E5 U+00E5 aring
|
||||
!E6 U+00E6 ae
|
||||
!E7 U+00E7 ccedilla
|
||||
!E8 U+00E8 egrave
|
||||
!E9 U+00E9 eacute
|
||||
!EA U+00EA ecircumflex
|
||||
!EB U+00EB edieresis
|
||||
!EC U+00EC igrave
|
||||
!ED U+00ED iacute
|
||||
!EE U+00EE icircumflex
|
||||
!EF U+00EF idieresis
|
||||
!F0 U+00F0 eth
|
||||
!F1 U+00F1 ntilde
|
||||
!F2 U+00F2 ograve
|
||||
!F3 U+00F3 oacute
|
||||
!F4 U+00F4 ocircumflex
|
||||
!F5 U+00F5 otilde
|
||||
!F6 U+00F6 odieresis
|
||||
!F7 U+00F7 divide
|
||||
!F8 U+00F8 oslash
|
||||
!F9 U+00F9 ugrave
|
||||
!FA U+00FA uacute
|
||||
!FB U+00FB ucircumflex
|
||||
!FC U+00FC udieresis
|
||||
!FD U+00FD yacute
|
||||
!FE U+00FE thorn
|
||||
!FF U+00FF ydieresis
|
||||
490
vendor/gonum.org/v1/plot/vg/vgpdf/vgpdf.go
generated
vendored
Normal file
490
vendor/gonum.org/v1/plot/vg/vgpdf/vgpdf.go
generated
vendored
Normal file
@@ -0,0 +1,490 @@
|
||||
// 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 vgpdf implements the vg.Canvas interface
|
||||
// using gofpdf (github.com/phpdave11/gofpdf).
|
||||
package vgpdf // import "gonum.org/v1/plot/vg/vgpdf"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
pdf "github.com/go-pdf/fpdf"
|
||||
stdfnt "golang.org/x/image/font"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// codePageEncoding holds informations about the characters encoding of TrueType
|
||||
// font files, needed by gofpdf to embed fonts in a PDF document.
|
||||
// We use cp1252 (code page 1252, Windows Western) to encode characters.
|
||||
// See:
|
||||
// - https://en.wikipedia.org/wiki/Windows-1252
|
||||
//
|
||||
// TODO: provide a Canvas-level func option to embed fonts with a user provided
|
||||
// code page schema?
|
||||
//
|
||||
//go:embed cp1252.map
|
||||
var codePageEncoding []byte
|
||||
|
||||
func init() {
|
||||
draw.RegisterFormat("pdf", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return New(w, h)
|
||||
})
|
||||
}
|
||||
|
||||
// DPI is the nominal resolution of drawing in PDF.
|
||||
const DPI = 72
|
||||
|
||||
// Canvas implements the vg.Canvas interface,
|
||||
// drawing to a PDF.
|
||||
type Canvas struct {
|
||||
doc *pdf.Fpdf
|
||||
w, h vg.Length
|
||||
|
||||
dpi int
|
||||
numImages int
|
||||
stack []context
|
||||
fonts map[font.Font]struct{}
|
||||
|
||||
// Switch to embed fonts in PDF file.
|
||||
// The default is to embed fonts.
|
||||
// This makes the PDF file more portable but also larger.
|
||||
embed bool
|
||||
}
|
||||
|
||||
type context struct {
|
||||
fill color.Color
|
||||
line color.Color
|
||||
width vg.Length
|
||||
}
|
||||
|
||||
// New creates a new PDF Canvas.
|
||||
func New(w, h vg.Length) *Canvas {
|
||||
cfg := pdf.InitType{
|
||||
UnitStr: "pt",
|
||||
Size: pdf.SizeType{Wd: w.Points(), Ht: h.Points()},
|
||||
}
|
||||
c := &Canvas{
|
||||
doc: pdf.NewCustom(&cfg),
|
||||
w: w,
|
||||
h: h,
|
||||
dpi: DPI,
|
||||
stack: make([]context, 1),
|
||||
fonts: make(map[font.Font]struct{}),
|
||||
embed: true,
|
||||
}
|
||||
c.NextPage()
|
||||
vg.Initialize(c)
|
||||
return c
|
||||
}
|
||||
|
||||
// EmbedFonts specifies whether the resulting PDF canvas should
|
||||
// embed the fonts or not.
|
||||
// EmbedFonts returns the previous value before modification.
|
||||
func (c *Canvas) EmbedFonts(v bool) bool {
|
||||
prev := c.embed
|
||||
c.embed = v
|
||||
return prev
|
||||
}
|
||||
|
||||
func (c *Canvas) DPI() float64 {
|
||||
return float64(c.dpi)
|
||||
}
|
||||
|
||||
func (c *Canvas) context() *context {
|
||||
return &c.stack[len(c.stack)-1]
|
||||
}
|
||||
|
||||
func (c *Canvas) Size() (w, h vg.Length) {
|
||||
return c.w, c.h
|
||||
}
|
||||
|
||||
func (c *Canvas) SetLineWidth(w vg.Length) {
|
||||
c.context().width = w
|
||||
lw := c.unit(w)
|
||||
c.doc.SetLineWidth(lw)
|
||||
}
|
||||
|
||||
func (c *Canvas) SetLineDash(dashes []vg.Length, offs vg.Length) {
|
||||
ds := make([]float64, len(dashes))
|
||||
for i, d := range dashes {
|
||||
ds[i] = c.unit(d)
|
||||
}
|
||||
c.doc.SetDashPattern(ds, c.unit(offs))
|
||||
}
|
||||
|
||||
func (c *Canvas) SetColor(clr color.Color) {
|
||||
if clr == nil {
|
||||
clr = color.Black
|
||||
}
|
||||
c.context().line = clr
|
||||
c.context().fill = clr
|
||||
r, g, b, a := rgba(clr)
|
||||
c.doc.SetFillColor(r, g, b)
|
||||
c.doc.SetDrawColor(r, g, b)
|
||||
c.doc.SetTextColor(r, g, b)
|
||||
c.doc.SetAlpha(a, "Normal")
|
||||
}
|
||||
|
||||
func (c *Canvas) Rotate(r float64) {
|
||||
c.doc.TransformRotate(-r*180/math.Pi, 0, 0)
|
||||
}
|
||||
|
||||
func (c *Canvas) Translate(pt vg.Point) {
|
||||
xp, yp := c.pdfPoint(pt)
|
||||
c.doc.TransformTranslate(xp, yp)
|
||||
}
|
||||
|
||||
func (c *Canvas) Scale(x float64, y float64) {
|
||||
c.doc.TransformScale(x*100, y*100, 0, 0)
|
||||
}
|
||||
|
||||
func (c *Canvas) Push() {
|
||||
c.stack = append(c.stack, *c.context())
|
||||
c.doc.TransformBegin()
|
||||
}
|
||||
|
||||
func (c *Canvas) Pop() {
|
||||
c.doc.TransformEnd()
|
||||
c.stack = c.stack[:len(c.stack)-1]
|
||||
}
|
||||
|
||||
func (c *Canvas) Stroke(p vg.Path) {
|
||||
if c.context().width > 0 {
|
||||
c.pdfPath(p, "D")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Canvas) Fill(p vg.Path) {
|
||||
c.pdfPath(p, "F")
|
||||
}
|
||||
|
||||
func (c *Canvas) FillString(fnt font.Face, pt vg.Point, str string) {
|
||||
if fnt.Font.Size == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c.font(fnt, pt)
|
||||
style := ""
|
||||
if fnt.Font.Weight == stdfnt.WeightBold {
|
||||
style += "B"
|
||||
}
|
||||
if fnt.Font.Style == stdfnt.StyleItalic {
|
||||
style += "I"
|
||||
}
|
||||
c.doc.SetFont(fnt.Name(), style, c.unit(fnt.Font.Size))
|
||||
|
||||
c.Push()
|
||||
defer c.Pop()
|
||||
c.Translate(pt)
|
||||
// go-fpdf uses the top left corner as origin.
|
||||
c.Scale(1, -1)
|
||||
left, top, right, bottom := c.sbounds(fnt, str)
|
||||
w := right - left
|
||||
h := bottom - top
|
||||
margin := c.doc.GetCellMargin()
|
||||
|
||||
c.doc.MoveTo(-left-margin, top)
|
||||
c.doc.CellFormat(w, h, str, "", 0, "BL", false, 0, "")
|
||||
}
|
||||
|
||||
func (c *Canvas) sbounds(fnt font.Face, txt string) (left, top, right, bottom float64) {
|
||||
_, h := c.doc.GetFontSize()
|
||||
style := ""
|
||||
if fnt.Font.Weight == stdfnt.WeightBold {
|
||||
style += "B"
|
||||
}
|
||||
if fnt.Font.Style == stdfnt.StyleItalic {
|
||||
style += "I"
|
||||
}
|
||||
d := c.doc.GetFontDesc(fnt.Name(), style)
|
||||
if d.Ascent == 0 {
|
||||
// not defined (standard font?), use average of 81%
|
||||
top = 0.81 * h
|
||||
} else {
|
||||
top = -float64(d.Ascent) * h / float64(d.Ascent-d.Descent)
|
||||
}
|
||||
return 0, top, c.doc.GetStringWidth(txt), top + h
|
||||
}
|
||||
|
||||
// DrawImage implements the vg.Canvas.DrawImage method.
|
||||
func (c *Canvas) DrawImage(rect vg.Rectangle, img image.Image) {
|
||||
opts := pdf.ImageOptions{ImageType: "png", ReadDpi: true}
|
||||
name := c.imageName()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
err := png.Encode(buf, img)
|
||||
if err != nil {
|
||||
log.Panicf("error encoding image to PNG: %v", err)
|
||||
}
|
||||
c.doc.RegisterImageOptionsReader(name, opts, buf)
|
||||
|
||||
xp, yp := c.pdfPoint(rect.Min)
|
||||
wp, hp := c.pdfPoint(rect.Size())
|
||||
|
||||
c.doc.ImageOptions(name, xp, yp, wp, hp, false, opts, 0, "")
|
||||
}
|
||||
|
||||
// font registers a font and a size with the PDF canvas.
|
||||
func (c *Canvas) font(fnt font.Face, pt vg.Point) {
|
||||
if _, ok := c.fonts[fnt.Font]; ok {
|
||||
return
|
||||
}
|
||||
name := fnt.Name()
|
||||
key := fontKey{font: fnt, embed: c.embed}
|
||||
raw := new(bytes.Buffer)
|
||||
_, err := fnt.Face.WriteSourceTo(nil, raw)
|
||||
if err != nil {
|
||||
log.Panicf("vgpdf: could not generate font %q data for PDF: %+v", name, err)
|
||||
}
|
||||
|
||||
zdata, jdata, err := getFont(key, raw.Bytes(), codePageEncoding)
|
||||
if err != nil {
|
||||
log.Panicf("vgpdf: could not generate font data for PDF: %v", err)
|
||||
}
|
||||
|
||||
c.fonts[fnt.Font] = struct{}{}
|
||||
c.doc.AddFontFromBytes(name, "", jdata, zdata)
|
||||
}
|
||||
|
||||
// pdfPath processes a vg.Path and applies it to the canvas.
|
||||
func (c *Canvas) pdfPath(path vg.Path, style string) {
|
||||
var (
|
||||
xp float64
|
||||
yp float64
|
||||
)
|
||||
for _, comp := range path {
|
||||
switch comp.Type {
|
||||
case vg.MoveComp:
|
||||
xp, yp = c.pdfPoint(comp.Pos)
|
||||
c.doc.MoveTo(xp, yp)
|
||||
case vg.LineComp:
|
||||
c.doc.LineTo(c.pdfPoint(comp.Pos))
|
||||
case vg.ArcComp:
|
||||
c.arc(comp, style)
|
||||
case vg.CurveComp:
|
||||
px, py := c.pdfPoint(comp.Pos)
|
||||
switch len(comp.Control) {
|
||||
case 1:
|
||||
cx, cy := c.pdfPoint(comp.Control[0])
|
||||
c.doc.CurveTo(cx, cy, px, py)
|
||||
case 2:
|
||||
cx, cy := c.pdfPoint(comp.Control[0])
|
||||
dx, dy := c.pdfPoint(comp.Control[1])
|
||||
c.doc.CurveBezierCubicTo(cx, cy, dx, dy, px, py)
|
||||
default:
|
||||
panic("vgpdf: invalid number of control points")
|
||||
}
|
||||
case vg.CloseComp:
|
||||
c.doc.LineTo(xp, yp)
|
||||
c.doc.ClosePath()
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown path component type: %d\n", comp.Type))
|
||||
}
|
||||
}
|
||||
c.doc.DrawPath(style)
|
||||
}
|
||||
|
||||
func (c *Canvas) arc(comp vg.PathComp, style string) {
|
||||
x0 := comp.Pos.X + comp.Radius*vg.Length(math.Cos(comp.Start))
|
||||
y0 := comp.Pos.Y + comp.Radius*vg.Length(math.Sin(comp.Start))
|
||||
c.doc.LineTo(c.pdfPointXY(x0, y0))
|
||||
r := c.unit(comp.Radius)
|
||||
const deg = 180 / math.Pi
|
||||
angle := comp.Angle * deg
|
||||
beg := comp.Start * deg
|
||||
end := beg + angle
|
||||
x := c.unit(comp.Pos.X)
|
||||
y := c.unit(comp.Pos.Y)
|
||||
c.doc.Arc(x, y, r, r, angle, beg, end, style)
|
||||
x1 := comp.Pos.X + comp.Radius*vg.Length(math.Cos(comp.Start+comp.Angle))
|
||||
y1 := comp.Pos.Y + comp.Radius*vg.Length(math.Sin(comp.Start+comp.Angle))
|
||||
c.doc.MoveTo(c.pdfPointXY(x1, y1))
|
||||
}
|
||||
|
||||
func (c *Canvas) pdfPointXY(x, y vg.Length) (float64, float64) {
|
||||
return c.unit(x), c.unit(y)
|
||||
}
|
||||
|
||||
func (c *Canvas) pdfPoint(pt vg.Point) (float64, float64) {
|
||||
return c.unit(pt.X), c.unit(pt.Y)
|
||||
}
|
||||
|
||||
// unit returns a fpdf.Unit, converted from a vg.Length.
|
||||
func (c *Canvas) unit(l vg.Length) float64 {
|
||||
return l.Dots(c.DPI())
|
||||
}
|
||||
|
||||
// imageName generates a unique image name for this PDF canvas
|
||||
func (c *Canvas) imageName() string {
|
||||
c.numImages++
|
||||
return fmt.Sprintf("image_%03d.png", c.numImages)
|
||||
}
|
||||
|
||||
// WriterCounter implements the io.Writer interface, and counts
|
||||
// the total number of bytes written.
|
||||
type writerCounter struct {
|
||||
io.Writer
|
||||
n int64
|
||||
}
|
||||
|
||||
func (w *writerCounter) Write(p []byte) (int, error) {
|
||||
n, err := w.Writer.Write(p)
|
||||
w.n += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// WriteTo writes the Canvas to an io.Writer.
|
||||
// After calling Write, the canvas is closed
|
||||
// and may no longer be used for drawing.
|
||||
func (c *Canvas) WriteTo(w io.Writer) (int64, error) {
|
||||
c.Pop()
|
||||
c.doc.Close()
|
||||
wc := writerCounter{Writer: w}
|
||||
b := bufio.NewWriter(&wc)
|
||||
if err := c.doc.Output(b); err != nil {
|
||||
return wc.n, err
|
||||
}
|
||||
err := b.Flush()
|
||||
return wc.n, err
|
||||
}
|
||||
|
||||
// rgba converts a Go color into a gofpdf 3-tuple int + 1 float64
|
||||
func rgba(c color.Color) (int, int, int, float64) {
|
||||
if c == nil {
|
||||
c = color.Black
|
||||
}
|
||||
r, g, b, a := c.RGBA()
|
||||
return int(r >> 8), int(g >> 8), int(b >> 8), float64(a) / math.MaxUint16
|
||||
}
|
||||
|
||||
type fontsCache struct {
|
||||
sync.RWMutex
|
||||
cache map[fontKey]fontVal
|
||||
}
|
||||
|
||||
// fontKey represents a PDF font request.
|
||||
// fontKey needs to know whether the font will be embedded or not,
|
||||
// as gofpdf.MakeFont will generate different informations.
|
||||
type fontKey struct {
|
||||
font font.Face
|
||||
embed bool
|
||||
}
|
||||
|
||||
type fontVal struct {
|
||||
z, j []byte
|
||||
}
|
||||
|
||||
func (c *fontsCache) get(key fontKey) (fontVal, bool) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
v, ok := c.cache[key]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
func (c *fontsCache) add(k fontKey, v fontVal) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.cache[k] = v
|
||||
}
|
||||
|
||||
var pdfFonts = &fontsCache{
|
||||
cache: make(map[fontKey]fontVal),
|
||||
}
|
||||
|
||||
func getFont(key fontKey, font, encoding []byte) (z, j []byte, err error) {
|
||||
if v, ok := pdfFonts.get(key); ok {
|
||||
return v.z, v.j, nil
|
||||
}
|
||||
|
||||
v, err := makeFont(key, font, encoding)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return v.z, v.j, nil
|
||||
}
|
||||
|
||||
func makeFont(key fontKey, font, encoding []byte) (val fontVal, err error) {
|
||||
tmpdir, err := os.MkdirTemp("", "gofpdf-makefont-")
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
indir := filepath.Join(tmpdir, "input")
|
||||
err = os.Mkdir(indir, 0755)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
||||
outdir := filepath.Join(tmpdir, "output")
|
||||
err = os.Mkdir(outdir, 0755)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
||||
fname := filepath.Join(indir, "font.ttf")
|
||||
encname := filepath.Join(indir, "cp1252.map")
|
||||
|
||||
err = os.WriteFile(fname, font, 0644)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
||||
err = os.WriteFile(encname, encoding, 0644)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
||||
err = pdf.MakeFont(fname, encname, outdir, io.Discard, key.embed)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
||||
if key.embed {
|
||||
z, err := os.ReadFile(filepath.Join(outdir, "font.z"))
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
val.z = z
|
||||
}
|
||||
|
||||
j, err := os.ReadFile(filepath.Join(outdir, "font.json"))
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
val.j = j
|
||||
|
||||
pdfFonts.add(key, val)
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// NextPage creates a new page in the final PDF document.
|
||||
// The new page is the new current page.
|
||||
// Modifications applied to the canvas will only be applied to that new page.
|
||||
func (c *Canvas) NextPage() {
|
||||
if c.doc.PageNo() > 0 {
|
||||
c.Pop()
|
||||
}
|
||||
c.doc.SetMargins(0, 0, 0)
|
||||
c.doc.AddPage()
|
||||
c.Push()
|
||||
c.Translate(vg.Point{X: 0, Y: c.h})
|
||||
c.Scale(1, -1)
|
||||
}
|
||||
650
vendor/gonum.org/v1/plot/vg/vgsvg/vgsvg.go
generated
vendored
Normal file
650
vendor/gonum.org/v1/plot/vg/vgsvg/vgsvg.go
generated
vendored
Normal file
@@ -0,0 +1,650 @@
|
||||
// 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 vgsvg uses svgo (github.com/ajstarks/svgo)
|
||||
// as a backend for vg.
|
||||
//
|
||||
// By default, gonum/plot uses the Liberation fonts.
|
||||
// When embedding was not requested during plot creation, it may happen that
|
||||
// the generated SVG plot may not display well if the Liberation fonts are not
|
||||
// available to the program displaying the SVG plot.
|
||||
// See gonum.org/v1/plot/vg/vgsvg#Example_standardFonts for how to work around
|
||||
// this issue.
|
||||
//
|
||||
// Alternatively, users may want to install the Liberation fonts on their system:
|
||||
// - https://en.wikipedia.org/wiki/Liberation_fonts
|
||||
package vgsvg // import "gonum.org/v1/plot/vg/vgsvg"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"html"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"io"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
svgo "github.com/ajstarks/svgo"
|
||||
xfnt "golang.org/x/image/font"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
func init() {
|
||||
draw.RegisterFormat("svg", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return New(w, h)
|
||||
})
|
||||
}
|
||||
|
||||
// pr is the precision to use when outputting float64s.
|
||||
const pr = 5
|
||||
|
||||
const (
|
||||
// DefaultWidth and DefaultHeight are the default canvas
|
||||
// dimensions.
|
||||
DefaultWidth = 4 * vg.Inch
|
||||
DefaultHeight = 4 * vg.Inch
|
||||
)
|
||||
|
||||
// Canvas implements the vg.Canvas interface, drawing to a SVG document.
|
||||
//
|
||||
// By default, fonts used by the canvas are not embedded in the produced
|
||||
// SVG document. This results in smaller but less portable SVG plots.
|
||||
// Users wanting completely portable SVG documents should create SVG canvases
|
||||
// with the EmbedFonts function.
|
||||
type Canvas struct {
|
||||
svg *svgo.SVG
|
||||
w, h vg.Length
|
||||
|
||||
hdr *bytes.Buffer // hdr is the SVG prelude, it may contain embedded fonts.
|
||||
buf *bytes.Buffer // buf is the SVG document.
|
||||
stack []context
|
||||
|
||||
// Switch to embed fonts in SVG file.
|
||||
// The default is to *not* embed fonts.
|
||||
// Embedding fonts makes the SVG file larger but also more portable.
|
||||
embed bool
|
||||
fonts map[string]struct{} // set of already embedded fonts
|
||||
}
|
||||
|
||||
type context struct {
|
||||
color color.Color
|
||||
dashArray []vg.Length
|
||||
dashOffset vg.Length
|
||||
lineWidth vg.Length
|
||||
gEnds int
|
||||
}
|
||||
|
||||
type option func(*Canvas)
|
||||
|
||||
// UseWH specifies the width and height of the canvas.
|
||||
func UseWH(w, h vg.Length) option {
|
||||
return func(c *Canvas) {
|
||||
if w <= 0 || h <= 0 {
|
||||
panic("vgsvg: w and h must both be > 0")
|
||||
}
|
||||
c.w = w
|
||||
c.h = h
|
||||
}
|
||||
}
|
||||
|
||||
// EmbedFonts specifies whether fonts should be embedded inside
|
||||
// the SVG canvas.
|
||||
func EmbedFonts(v bool) option {
|
||||
return func(c *Canvas) {
|
||||
c.embed = v
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new image canvas.
|
||||
func New(w, h vg.Length) *Canvas {
|
||||
return NewWith(UseWH(w, h))
|
||||
}
|
||||
|
||||
// NewWith returns a new image canvas created according to the specified
|
||||
// options. The currently accepted options is UseWH. If size is not
|
||||
// specified, the default is used.
|
||||
func NewWith(opts ...option) *Canvas {
|
||||
buf := new(bytes.Buffer)
|
||||
c := &Canvas{
|
||||
svg: svgo.New(buf),
|
||||
w: DefaultWidth,
|
||||
h: DefaultHeight,
|
||||
hdr: new(bytes.Buffer),
|
||||
buf: buf,
|
||||
stack: []context{{}},
|
||||
embed: false,
|
||||
fonts: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
|
||||
// This is like svg.Start, except it uses floats
|
||||
// and specifies the units.
|
||||
fmt.Fprintf(c.hdr, `<?xml version="1.0"?>
|
||||
<!-- Generated by SVGo and Plotinum VG -->
|
||||
<svg width="%.*gpt" height="%.*gpt" viewBox="0 0 %.*g %.*g"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">`+"\n",
|
||||
pr, c.w,
|
||||
pr, c.h,
|
||||
pr, c.w,
|
||||
pr, c.h,
|
||||
)
|
||||
|
||||
if c.embed {
|
||||
fmt.Fprintf(c.hdr, "<defs>\n\t<style>\n")
|
||||
}
|
||||
|
||||
// Swap the origin to the bottom left.
|
||||
// This must be matched with a </g> when saving,
|
||||
// before the closing </svg>.
|
||||
c.svg.Gtransform(fmt.Sprintf("scale(1, -1) translate(0, -%.*g)", pr, c.h.Points()))
|
||||
|
||||
vg.Initialize(c)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Canvas) Size() (w, h vg.Length) {
|
||||
return c.w, c.h
|
||||
}
|
||||
|
||||
func (c *Canvas) context() *context {
|
||||
return &c.stack[len(c.stack)-1]
|
||||
}
|
||||
|
||||
func (c *Canvas) SetLineWidth(w vg.Length) {
|
||||
c.context().lineWidth = w
|
||||
}
|
||||
|
||||
func (c *Canvas) SetLineDash(dashes []vg.Length, offs vg.Length) {
|
||||
c.context().dashArray = dashes
|
||||
c.context().dashOffset = offs
|
||||
}
|
||||
|
||||
func (c *Canvas) SetColor(clr color.Color) {
|
||||
c.context().color = clr
|
||||
}
|
||||
|
||||
func (c *Canvas) Rotate(rot float64) {
|
||||
rot = rot * 180 / math.Pi
|
||||
c.svg.Rotate(rot)
|
||||
c.context().gEnds++
|
||||
}
|
||||
|
||||
func (c *Canvas) Translate(pt vg.Point) {
|
||||
c.svg.Gtransform(fmt.Sprintf("translate(%.*g, %.*g)", pr, pt.X.Points(), pr, pt.Y.Points()))
|
||||
c.context().gEnds++
|
||||
}
|
||||
|
||||
func (c *Canvas) Scale(x, y float64) {
|
||||
c.svg.ScaleXY(x, y)
|
||||
c.context().gEnds++
|
||||
}
|
||||
|
||||
func (c *Canvas) Push() {
|
||||
top := *c.context()
|
||||
top.gEnds = 0
|
||||
c.stack = append(c.stack, top)
|
||||
}
|
||||
|
||||
func (c *Canvas) Pop() {
|
||||
for i := 0; i < c.context().gEnds; i++ {
|
||||
c.svg.Gend()
|
||||
}
|
||||
c.stack = c.stack[:len(c.stack)-1]
|
||||
}
|
||||
|
||||
func (c *Canvas) Stroke(path vg.Path) {
|
||||
if c.context().lineWidth.Points() <= 0 {
|
||||
return
|
||||
}
|
||||
c.svg.Path(c.pathData(path),
|
||||
style(elm("fill", "#000000", "none"),
|
||||
elm("stroke", "none", colorString(c.context().color)),
|
||||
elm("stroke-opacity", "1", opacityString(c.context().color)),
|
||||
elm("stroke-width", "1", "%.*g", pr, c.context().lineWidth.Points()),
|
||||
elm("stroke-dasharray", "none", dashArrayString(c)),
|
||||
elm("stroke-dashoffset", "0", "%.*g", pr, c.context().dashOffset.Points())))
|
||||
}
|
||||
|
||||
func (c *Canvas) Fill(path vg.Path) {
|
||||
c.svg.Path(c.pathData(path),
|
||||
style(elm("fill", "#000000", colorString(c.context().color)),
|
||||
elm("fill-opacity", "1", opacityString(c.context().color))))
|
||||
}
|
||||
|
||||
func (c *Canvas) pathData(path vg.Path) string {
|
||||
buf := new(bytes.Buffer)
|
||||
var x, y float64
|
||||
for _, comp := range path {
|
||||
switch comp.Type {
|
||||
case vg.MoveComp:
|
||||
fmt.Fprintf(buf, "M%.*g,%.*g", pr, comp.Pos.X.Points(), pr, comp.Pos.Y.Points())
|
||||
x = comp.Pos.X.Points()
|
||||
y = comp.Pos.Y.Points()
|
||||
case vg.LineComp:
|
||||
fmt.Fprintf(buf, "L%.*g,%.*g", pr, comp.Pos.X.Points(), pr, comp.Pos.Y.Points())
|
||||
x = comp.Pos.X.Points()
|
||||
y = comp.Pos.Y.Points()
|
||||
case vg.ArcComp:
|
||||
r := comp.Radius.Points()
|
||||
sin, cos := math.Sincos(comp.Start)
|
||||
x0 := comp.Pos.X.Points() + r*cos
|
||||
y0 := comp.Pos.Y.Points() + r*sin
|
||||
if x0 != x || y0 != y {
|
||||
fmt.Fprintf(buf, "L%.*g,%.*g", pr, x0, pr, y0)
|
||||
}
|
||||
if math.Abs(comp.Angle) >= 2*math.Pi {
|
||||
x, y = circle(buf, c, &comp)
|
||||
} else {
|
||||
x, y = arc(buf, c, &comp)
|
||||
}
|
||||
case vg.CurveComp:
|
||||
switch len(comp.Control) {
|
||||
case 1:
|
||||
fmt.Fprintf(buf, "Q%.*g,%.*g,%.*g,%.*g",
|
||||
pr, comp.Control[0].X.Points(), pr, comp.Control[0].Y.Points(),
|
||||
pr, comp.Pos.X.Points(), pr, comp.Pos.Y.Points())
|
||||
case 2:
|
||||
fmt.Fprintf(buf, "C%.*g,%.*g,%.*g,%.*g,%.*g,%.*g",
|
||||
pr, comp.Control[0].X.Points(), pr, comp.Control[0].Y.Points(),
|
||||
pr, comp.Control[1].X.Points(), pr, comp.Control[1].Y.Points(),
|
||||
pr, comp.Pos.X.Points(), pr, comp.Pos.Y.Points())
|
||||
default:
|
||||
panic("vgsvg: invalid number of control points")
|
||||
}
|
||||
x = comp.Pos.X.Points()
|
||||
y = comp.Pos.Y.Points()
|
||||
case vg.CloseComp:
|
||||
buf.WriteString("Z")
|
||||
default:
|
||||
panic(fmt.Sprintf("vgsvg: unknown path component type: %d", comp.Type))
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// circle adds circle path data to the given writer.
|
||||
// Circles must be drawn using two arcs because
|
||||
// SVG disallows the start and end point of an arc
|
||||
// from being at the same location.
|
||||
func circle(w io.Writer, c *Canvas, comp *vg.PathComp) (x, y float64) {
|
||||
angle := 2 * math.Pi
|
||||
if comp.Angle < 0 {
|
||||
angle = -2 * math.Pi
|
||||
}
|
||||
angle += remainder(comp.Angle, 2*math.Pi)
|
||||
if angle >= 4*math.Pi {
|
||||
panic("Impossible angle")
|
||||
}
|
||||
|
||||
s0, c0 := math.Sincos(comp.Start + 0.5*angle)
|
||||
s1, c1 := math.Sincos(comp.Start + angle)
|
||||
|
||||
r := comp.Radius.Points()
|
||||
x0 := comp.Pos.X.Points() + r*c0
|
||||
y0 := comp.Pos.Y.Points() + r*s0
|
||||
x = comp.Pos.X.Points() + r*c1
|
||||
y = comp.Pos.Y.Points() + r*s1
|
||||
|
||||
fmt.Fprintf(w, "A%.*g,%.*g 0 %d %d %.*g,%.*g", pr, r, pr, r,
|
||||
large(angle/2), sweep(angle/2), pr, x0, pr, y0) //
|
||||
fmt.Fprintf(w, "A%.*g,%.*g 0 %d %d %.*g,%.*g", pr, r, pr, r,
|
||||
large(angle/2), sweep(angle/2), pr, x, pr, y)
|
||||
return
|
||||
}
|
||||
|
||||
// remainder returns the remainder of x/y.
|
||||
// We don't use math.Remainder because it
|
||||
// seems to return incorrect values due to how
|
||||
// IEEE defines the remainder operation…
|
||||
func remainder(x, y float64) float64 {
|
||||
return (x/y - math.Trunc(x/y)) * y
|
||||
}
|
||||
|
||||
// arc adds arc path data to the given writer.
|
||||
// Arc can only be used if the arc's angle is
|
||||
// less than a full circle, if it is greater then
|
||||
// circle should be used instead.
|
||||
func arc(w io.Writer, c *Canvas, comp *vg.PathComp) (x, y float64) {
|
||||
r := comp.Radius.Points()
|
||||
sin, cos := math.Sincos(comp.Start + comp.Angle)
|
||||
x = comp.Pos.X.Points() + r*cos
|
||||
y = comp.Pos.Y.Points() + r*sin
|
||||
fmt.Fprintf(w, "A%.*g,%.*g 0 %d %d %.*g,%.*g", pr, r, pr, r,
|
||||
large(comp.Angle), sweep(comp.Angle), pr, x, pr, y)
|
||||
return
|
||||
}
|
||||
|
||||
// sweep returns the arc sweep flag value for
|
||||
// the given angle.
|
||||
func sweep(a float64) int {
|
||||
if a < 0 {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// large returns the arc's large flag value for
|
||||
// the given angle.
|
||||
func large(a float64) int {
|
||||
if math.Abs(a) >= math.Pi {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// FillString draws str at position pt using the specified font.
|
||||
// Text passed to FillString is escaped with html.EscapeString.
|
||||
func (c *Canvas) FillString(font font.Face, pt vg.Point, str string) {
|
||||
name := svgFontDescr(font)
|
||||
sty := style(
|
||||
name,
|
||||
elm("font-size", "medium", "%.*gpx", pr, font.Font.Size.Points()),
|
||||
elm("fill", "#000000", colorString(c.context().color)),
|
||||
)
|
||||
if sty != "" {
|
||||
sty = "\n\t" + sty
|
||||
}
|
||||
fmt.Fprintf(
|
||||
c.buf,
|
||||
`<text x="%.*g" y="%.*g" transform="scale(1, -1)"%s>%s</text>`+"\n",
|
||||
pr, pt.X.Points(), pr, -pt.Y.Points(), sty, html.EscapeString(str),
|
||||
)
|
||||
|
||||
if c.embed {
|
||||
c.embedFont(name, font)
|
||||
}
|
||||
}
|
||||
|
||||
// DrawImage implements the vg.Canvas.DrawImage method.
|
||||
func (c *Canvas) DrawImage(rect vg.Rectangle, img image.Image) {
|
||||
buf := new(bytes.Buffer)
|
||||
err := png.Encode(buf, img)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("vgsvg: error encoding image to PNG: %+v", err))
|
||||
}
|
||||
str := "data:image/jpg;base64," + base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||
rsz := rect.Size()
|
||||
min := rect.Min
|
||||
var (
|
||||
width = rsz.X.Points()
|
||||
height = rsz.Y.Points()
|
||||
xmin = min.X.Points()
|
||||
ymin = min.Y.Points()
|
||||
)
|
||||
fmt.Fprintf(
|
||||
c.buf,
|
||||
`<image x="%v" y="%v" width="%v" height="%v" xlink:href="%s" %s />`+"\n",
|
||||
xmin,
|
||||
-ymin-height,
|
||||
width,
|
||||
height,
|
||||
str,
|
||||
// invert y so image is not upside-down
|
||||
`transform="scale(1, -1)"`,
|
||||
)
|
||||
}
|
||||
|
||||
// svgFontDescr returns a SVG compliant font name from the provided font face.
|
||||
func svgFontDescr(fnt font.Face) string {
|
||||
var (
|
||||
family = svgFamilyName(fnt)
|
||||
variant = svgVariantName(fnt.Font.Variant)
|
||||
style = svgStyleName(fnt.Font.Style)
|
||||
weight = svgWeightName(fnt.Font.Weight)
|
||||
)
|
||||
|
||||
o := "font-family:" + family + ";" +
|
||||
"font-variant:" + variant + ";" +
|
||||
"font-weight:" + weight + ";" +
|
||||
"font-style:" + style
|
||||
return o
|
||||
}
|
||||
|
||||
func svgFamilyName(fnt font.Face) string {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-family
|
||||
var buf sfnt.Buffer
|
||||
name, err := fnt.Face.Name(&buf, sfnt.NameIDFamily)
|
||||
if err != nil {
|
||||
// this should never happen unless the underlying sfnt.Font data
|
||||
// is somehow corrupted.
|
||||
panic(fmt.Errorf(
|
||||
"vgsvg: could not extract family name from font %q: %+v",
|
||||
fnt.Font.Typeface,
|
||||
err,
|
||||
))
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func svgVariantName(v font.Variant) string {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-variant
|
||||
str := strings.ToLower(string(v))
|
||||
switch str {
|
||||
case "smallcaps":
|
||||
return "small-caps"
|
||||
case "mono", "monospace",
|
||||
"sans", "sansserif", "sans-serif",
|
||||
"serif":
|
||||
// handle mismatch between the meaning of gonum/plot/font.Font#Variant
|
||||
// and SVG's meaning for font-variant.
|
||||
// For SVG, mono, ... serif is encoded in the font-family attribute
|
||||
// whereas for gonum/plot it describes a variant among a collection of fonts.
|
||||
//
|
||||
// It shouldn't matter much if an invalid font-variant value is written
|
||||
// out (browsers will just ignore it; Firefox 98 and Chromium 91 do so.)
|
||||
return "normal"
|
||||
case "":
|
||||
return "none"
|
||||
default:
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
func svgStyleName(sty xfnt.Style) string {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-style
|
||||
switch sty {
|
||||
case xfnt.StyleNormal:
|
||||
return "normal"
|
||||
case xfnt.StyleItalic:
|
||||
return "italic"
|
||||
case xfnt.StyleOblique:
|
||||
return "oblique"
|
||||
default:
|
||||
panic(fmt.Errorf("vgsvg: invalid font style %+v (v=%d)", sty, int(sty)))
|
||||
}
|
||||
}
|
||||
|
||||
func svgWeightName(w xfnt.Weight) string {
|
||||
// see:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-weight
|
||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight
|
||||
switch w {
|
||||
case xfnt.WeightThin:
|
||||
return "100"
|
||||
case xfnt.WeightExtraLight:
|
||||
return "200"
|
||||
case xfnt.WeightLight:
|
||||
return "300"
|
||||
case xfnt.WeightNormal:
|
||||
return "normal"
|
||||
case xfnt.WeightMedium:
|
||||
return "500"
|
||||
case xfnt.WeightSemiBold:
|
||||
return "600"
|
||||
case xfnt.WeightBold:
|
||||
return "bold"
|
||||
case xfnt.WeightExtraBold:
|
||||
return "800"
|
||||
case xfnt.WeightBlack:
|
||||
return "900"
|
||||
default:
|
||||
panic(fmt.Errorf("vgsvg: invalid font weight %+v (v=%d)", w, int(w)))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Canvas) embedFont(name string, f font.Face) {
|
||||
if _, dup := c.fonts[name]; dup {
|
||||
return
|
||||
}
|
||||
c.fonts[name] = struct{}{}
|
||||
|
||||
raw := new(bytes.Buffer)
|
||||
_, err := f.Face.WriteSourceTo(nil, raw)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("vg/vgsvg: could not read font raw data: %+v", err))
|
||||
}
|
||||
|
||||
fmt.Fprintf(c.hdr, "\t\t@font-face{\n")
|
||||
fmt.Fprintf(c.hdr, "\t\t\tfont-family:%q;\n", svgFamilyName(f))
|
||||
fmt.Fprintf(c.hdr,
|
||||
"\t\t\tfont-variant:%s;font-weight:%s;font-style:%s;\n",
|
||||
svgVariantName(f.Font.Variant),
|
||||
svgWeightName(f.Font.Weight),
|
||||
svgStyleName(f.Font.Style),
|
||||
)
|
||||
|
||||
fmt.Fprintf(
|
||||
c.hdr,
|
||||
"\t\t\tsrc: url(data:font/ttf;charset=utf-8;base64,%s) format(\"truetype\");\n",
|
||||
base64.StdEncoding.EncodeToString(raw.Bytes()),
|
||||
)
|
||||
fmt.Fprintf(c.hdr, "\t\t}\n")
|
||||
}
|
||||
|
||||
type cwriter struct {
|
||||
w *bufio.Writer
|
||||
n int64
|
||||
}
|
||||
|
||||
func (c *cwriter) Write(p []byte) (int, error) {
|
||||
n, err := c.w.Write(p)
|
||||
c.n += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// WriteTo writes the canvas to an io.Writer.
|
||||
func (c *Canvas) WriteTo(w io.Writer) (int64, error) {
|
||||
b := &cwriter{w: bufio.NewWriter(w)}
|
||||
|
||||
if c.embed {
|
||||
fmt.Fprintf(c.hdr, "\t</style>\n</defs>\n")
|
||||
}
|
||||
|
||||
_, err := c.hdr.WriteTo(b)
|
||||
if err != nil {
|
||||
return b.n, err
|
||||
}
|
||||
|
||||
_, err = c.buf.WriteTo(b)
|
||||
if err != nil {
|
||||
return b.n, err
|
||||
}
|
||||
|
||||
// Close the groups and svg in the output buffer
|
||||
// so that the Canvas is not closed and can be
|
||||
// used again if needed.
|
||||
for i := 0; i < c.nEnds(); i++ {
|
||||
_, err = fmt.Fprintln(b, "</g>")
|
||||
if err != nil {
|
||||
return b.n, err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintln(b, "</svg>")
|
||||
if err != nil {
|
||||
return b.n, err
|
||||
}
|
||||
|
||||
return b.n, b.w.Flush()
|
||||
}
|
||||
|
||||
// nEnds returns the number of group ends
|
||||
// needed before the SVG is saved.
|
||||
func (c *Canvas) nEnds() int {
|
||||
n := 1 // close the transform that moves the origin
|
||||
for _, ctx := range c.stack {
|
||||
n += ctx.gEnds
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// style returns a style string composed of
|
||||
// all of the given elements. If the elements
|
||||
// are all empty then the empty string is
|
||||
// returned.
|
||||
func style(elms ...string) string {
|
||||
str := ""
|
||||
for _, e := range elms {
|
||||
if e == "" {
|
||||
continue
|
||||
}
|
||||
if str != "" {
|
||||
str += ";"
|
||||
}
|
||||
str += e
|
||||
}
|
||||
if str == "" {
|
||||
return ""
|
||||
}
|
||||
return "style=\"" + str + "\""
|
||||
}
|
||||
|
||||
// elm returns a style element string with the
|
||||
// given key and value. If the value matches
|
||||
// default then the empty string is returned.
|
||||
func elm(key, def, f string, vls ...interface{}) string {
|
||||
value := fmt.Sprintf(f, vls...)
|
||||
if value == def {
|
||||
return ""
|
||||
}
|
||||
return key + ":" + value
|
||||
}
|
||||
|
||||
// dashArrayString returns a string representing the
|
||||
// dash array specification.
|
||||
func dashArrayString(c *Canvas) string {
|
||||
str := ""
|
||||
for i, d := range c.context().dashArray {
|
||||
str += fmt.Sprintf("%.*g", pr, d.Points())
|
||||
if i < len(c.context().dashArray)-1 {
|
||||
str += ","
|
||||
}
|
||||
}
|
||||
if str == "" {
|
||||
str = "none"
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// colorString returns the hexadecimal string representation of the color
|
||||
func colorString(clr color.Color) string {
|
||||
if clr == nil {
|
||||
clr = color.Black
|
||||
}
|
||||
r, g, b, _a := clr.RGBA()
|
||||
a := 255.0 / float64(_a)
|
||||
return fmt.Sprintf("#%02X%02X%02X", int(float64(r)*a),
|
||||
int(float64(g)*a), int(float64(b)*a))
|
||||
}
|
||||
|
||||
// opacityString returns the opacity value of the given color.
|
||||
func opacityString(clr color.Color) string {
|
||||
if clr == nil {
|
||||
clr = color.Black
|
||||
}
|
||||
_, _, _, a := clr.RGBA()
|
||||
return fmt.Sprintf("%.*g", pr, float64(a)/math.MaxUint16)
|
||||
}
|
||||
329
vendor/gonum.org/v1/plot/vg/vgtex/canvas.go
generated
vendored
Normal file
329
vendor/gonum.org/v1/plot/vg/vgtex/canvas.go
generated
vendored
Normal file
@@ -0,0 +1,329 @@
|
||||
// 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()
|
||||
}
|
||||
Reference in New Issue
Block a user