)
+
+The Offcolor type:
+
+ type Offcolor struct {
+ Offset uint8
+ Color string
+ Opacity float
+ }
+
+is used to specify the offset, color, and opacity of stop colors in linear and radial gradients
+
+The Filterspec type:
+
+ type Filterspec struct {
+ In string
+ In2 string
+ Result string
+ }
+
+is used to specify inputs and results for filter effects
+
+*/
+package svg
diff --git a/vendor/github.com/ajstarks/svgo/f50/f50.go b/vendor/github.com/ajstarks/svgo/f50/f50.go
new file mode 100644
index 0000000..3f96a1c
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/f50/f50.go
@@ -0,0 +1,120 @@
+// f50 -- given a search term, display 10x5 image grid, sorted by interestingness
+// +build !appengine
+
+package main
+
+import (
+ "encoding/xml"
+ "fmt"
+ "net/http"
+ "net/url"
+ "os"
+
+ "github.com/ajstarks/svgo"
+)
+
+// FlickrResp defines the Flickr response
+type FlickrResp struct {
+ Stat string `xml:"stat,attr"`
+ Photos Photos `xml:"photos"`
+}
+
+// Photos defines a set of Flickr photos
+type Photos struct {
+ Page string `xml:"page,attr"`
+ Pages string `xml:"pages,attr"`
+ Perpage string `xml:"perpage,attr"`
+ Total string `xml:"total,attr"`
+ Photo []Photo `xml:"photo"`
+}
+
+// Photo defines a Flickr photo
+type Photo struct {
+ Id string `xml:"id,attr"`
+ Owner string `xml:"owner,attr"`
+ Secret string `xml:"secret,attr"`
+ Server string `xml:"server,attr"`
+ Farm string `xml:"farm,attr"`
+ Title string `xml:"title,attr"`
+ Ispublic string `xml:"ispublic,attr"`
+ Isfriend string `xml:"isfriend,attr"`
+ IsFamily string `xml:"isfamily,attr"`
+}
+
+var (
+ width = 805
+ height = 500
+ canvas = svg.New(os.Stdout)
+)
+
+const (
+ apifmt = "https://api.flickr.com/services/rest/?method=%s&api_key=%s&%s=%s&per_page=50&sort=interestingness-desc"
+ urifmt = "http://farm%s.static.flickr.com/%s/%s.jpg"
+ apiKey = "YOURKEY"
+ textStyle = "font-family:Calibri,sans-serif; font-size:48px; fill:white; text-anchor:start"
+ imageWidth = 75
+ imageHeight = 75
+)
+
+// FlickrAPI calls the API given a method with single name/value pair
+func flickrAPI(method, name, value string) string {
+ return fmt.Sprintf(apifmt, method, apiKey, name, value)
+}
+
+// makeURI converts the elements of a photo into a Flickr photo URI
+func makeURI(p Photo, imsize string) string {
+ im := p.Id + "_" + p.Secret
+
+ if len(imsize) > 0 {
+ im += "_" + imsize
+ }
+ return fmt.Sprintf(urifmt, p.Farm, p.Server, im)
+}
+
+// imageGrid reads the response from Flickr, and creates a grid of images
+func imageGrid(f FlickrResp, x, y, cols, gutter int, imgsize string) {
+ if f.Stat != "ok" {
+ fmt.Fprintf(os.Stderr, "Status: %v\n", f.Stat)
+ return
+ }
+ xpos := x
+ for i, p := range f.Photos.Photo {
+ if i%cols == 0 && i > 0 {
+ xpos = x
+ y += (imageHeight + gutter)
+ }
+ canvas.Link(makeURI(p, ""), p.Title)
+ canvas.Image(xpos, y, imageWidth, imageHeight, makeURI(p, "s"))
+ canvas.LinkEnd()
+ xpos += (imageWidth + gutter)
+ }
+}
+
+// fs calls the Flickr API to perform a photo search
+func fs(s string) {
+ var f FlickrResp
+ r, weberr := http.Get(flickrAPI("flickr.photos.search", "text", s))
+ if weberr != nil {
+ fmt.Fprintf(os.Stderr, "%v\n", weberr)
+ return
+ }
+ defer r.Body.Close()
+ xmlerr := xml.NewDecoder(r.Body).Decode(&f)
+ if xmlerr != nil || r.StatusCode != http.StatusOK {
+ fmt.Fprintf(os.Stderr, "%v (status=%d)\n", xmlerr, r.StatusCode)
+ return
+ }
+ canvas.Title(s)
+ imageGrid(f, 5, 5, 10, 5, "s")
+ canvas.Text(20, height-40, s, textStyle)
+}
+
+// for each search term on the commandline, create a photo grid
+func main() {
+ for i := 1; i < len(os.Args); i++ {
+ canvas.Start(width, height)
+ canvas.Rect(0, 0, width, height, "fill:black")
+ fs(url.QueryEscape(os.Args[i]))
+ canvas.End()
+ }
+}
diff --git a/vendor/github.com/ajstarks/svgo/fe/fe.go b/vendor/github.com/ajstarks/svgo/fe/fe.go
new file mode 100644
index 0000000..1681192
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/fe/fe.go
@@ -0,0 +1,53 @@
+// fe: SVG Filter Effect example from http://www.w3.org/TR/SVG/filters.html#AnExample
+// +build !appengine
+
+package main
+
+
+import (
+ "github.com/ajstarks/svgo"
+ "os"
+)
+
+func main() {
+
+ canvas := svg.New(os.Stdout)
+ width := 410
+ height := 120
+
+ canvas.Start(width, height)
+ canvas.Title(`SVGo Filter Example`)
+ canvas.Desc(`Combines multiple filter primitives to produce a 3D lighting effect`)
+
+ gfs := svg.Filterspec{In: "SourceAlpha", Result: "blur"}
+ ofs := svg.Filterspec{In: "blur", Result: "offsetBlur"}
+ sfs := svg.Filterspec{In: "blur", Result: "specOut"}
+ cfs1 := svg.Filterspec{In: "specOut", In2: "SourceAlpha", Result: "specOut"}
+ cfs2 := svg.Filterspec{In: "SourceGraphic", In2: "specOut", Result: "litPaint"}
+
+ // define the filters
+ canvas.Def()
+ canvas.Filter("myFilter")
+ canvas.FeGaussianBlur(gfs, 4, 4)
+ canvas.FeOffset(ofs, 4, 4)
+ canvas.FeSpecularLighting(sfs, 5, .75, 20, "#bbbbbb")
+ canvas.FePointLight(-5000, -10000, 20000)
+ canvas.FeSpecEnd()
+ canvas.FeComposite(cfs1, "in", 0, 0, 0, 0)
+ canvas.FeComposite(cfs2, "arithmetic", 0, 1, 1, 0)
+ canvas.FeMerge([]string{ofs.Result, cfs2.Result})
+ canvas.Fend()
+ canvas.DefEnd()
+
+ // specify the graphic
+ canvas.Gid("SVG")
+ canvas.Path("M50,90 C0,90 0,30 50,30 L150,30 C200,30 200,90 150,90 z", "fill:none;stroke:#D90000;stroke-width:10")
+ canvas.Path("M60,80 C30,80 30,40 60,40 L140,40 C170,40 170,80 140,80 z", "fill:#D90000")
+ canvas.Text(52, 76, "SVG", "fill:white;stroke:black;font-size:45;font-family:Verdana")
+ canvas.Gend()
+
+ canvas.Rect(0, 0, width, height, "stroke:black;fill:white")
+ canvas.Use(0, 0, "#SVG") // plain graphic
+ canvas.Use(200, 0, "#SVG", `filter="url(#myFilter)"`) // filter applied
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/flower/flower.go b/vendor/github.com/ajstarks/svgo/flower/flower.go
new file mode 100644
index 0000000..515ddb2
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/flower/flower.go
@@ -0,0 +1,79 @@
+// flower - draw random flowers, inspired by Evelyn Eastmond's DesignBlocks gererated "grain2"
+// +build !appengine
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "math"
+ "math/rand"
+ "os"
+ "time"
+
+ "github.com/ajstarks/svgo"
+)
+
+var (
+ canvas = svg.New(os.Stdout)
+ niter = flag.Int("n", 200, "number of iterations")
+ width = flag.Int("w", 500, "width")
+ height = flag.Int("h", 500, "height")
+ thickness = flag.Int("t", 10, "max petal thinkness")
+ np = flag.Int("p", 15, "max number of petals")
+ psize = flag.Int("s", 30, "max length of petals")
+ opacity = flag.Int("o", 50, "max opacity (10-100)")
+)
+
+const flowerfmt = `stroke:rgb(%d,%d,%d); stroke-opacity:%.2f; stroke-width:%d`
+
+func radial(xp int, yp int, n int, l int, style ...string) {
+ var x, y, r, t, limit float64
+ limit = 2.0 * math.Pi
+ r = float64(l)
+ canvas.Gstyle(style[0])
+ for t = 0.0; t < limit; t += limit / float64(n) {
+ x = r * math.Cos(t)
+ y = r * math.Sin(t)
+ canvas.Line(xp, yp, xp+int(x), yp+int(y))
+ }
+ canvas.Gend()
+}
+
+func random(howsmall, howbig int) int {
+ if howsmall >= howbig {
+ return howsmall
+ }
+ return rand.Intn(howbig-howsmall) + howsmall
+}
+
+func randrad(w int, h int, n int) {
+ var x, y, r, g, b, o, s, t, p int
+ for i := 0; i < n; i++ {
+ x = rand.Intn(w)
+ y = rand.Intn(h)
+ r = rand.Intn(255)
+ g = rand.Intn(255)
+ b = rand.Intn(255)
+ o = random(10, *opacity)
+ s = random(10, *psize)
+ t = random(2, *thickness)
+ p = random(10, *np)
+ radial(x, y, p, s, fmt.Sprintf(flowerfmt, r, g, b, float64(o)/100.0, t))
+ }
+}
+
+func background(v int) { canvas.Rect(0, 0, *width, *height, canvas.RGB(v, v, v)) }
+
+func init() {
+ flag.Parse()
+ rand.Seed(int64(time.Now().Nanosecond()) % 1e9)
+}
+
+func main() {
+ canvas.Start(*width, *height)
+ canvas.Title("Random Flowers")
+ background(255)
+ randrad(*width, *height, *niter)
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/fontcompare/fontcompare.go b/vendor/github.com/ajstarks/svgo/fontcompare/fontcompare.go
new file mode 100644
index 0000000..120aac1
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/fontcompare/fontcompare.go
@@ -0,0 +1,52 @@
+// fontcompare: compare two fonts
+// +build !appengine
+
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/ajstarks/svgo"
+)
+
+var (
+ canvas = svg.New(os.Stdout)
+ width = 1000
+ height = 600
+ chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789(){}[],.:;-+*/\\&_^%$#@!~`'\"<>"
+ gstyle = "font-family:%s;font-size:%dpt;text-anchor:middle;fill:%s;fill-opacity:%.2f"
+)
+
+func letters(top, left int, font, color string, opacity float32) {
+ rows := 7
+ cols := 13
+ glyph := 0
+ fontsize := 32
+ spacing := fontsize * 2
+ x := left
+ y := top
+ canvas.Gstyle(fmt.Sprintf(gstyle, font, fontsize, color, opacity))
+ for r := 0; r < rows; r++ {
+ for c := 0; c < cols; c++ {
+ canvas.Text(x, y, chars[glyph:glyph+1])
+ glyph++
+ x += spacing
+ }
+ x = left
+ y += spacing
+ }
+ canvas.Gend()
+}
+
+func main() {
+ if len(os.Args) > 2 {
+ canvas.Start(width, height)
+ canvas.Rect(0, 0, width, height, "fill:white")
+ canvas.Text(80, 540, os.Args[1], "font-size:14pt; fill:blue; font-family:"+os.Args[1])
+ canvas.Text(80, 560, os.Args[2], "font-size:14pt; fill:red; font-family:"+os.Args[2])
+ letters(100, 100, os.Args[1], "blue", 0.5)
+ letters(100, 100, os.Args[2], "red", 0.5)
+ canvas.End()
+ }
+}
diff --git a/vendor/github.com/ajstarks/svgo/funnel/funnel.go b/vendor/github.com/ajstarks/svgo/funnel/funnel.go
new file mode 100644
index 0000000..901a6bd
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/funnel/funnel.go
@@ -0,0 +1,29 @@
+// funnel draws a funnel-like shape
+// +build !appengine
+
+package main
+
+import (
+ "os"
+
+ "github.com/ajstarks/svgo"
+)
+
+var canvas = svg.New(os.Stdout)
+var width = 320
+var height = 480
+
+func funnel(bg int, fg int, grid int, dim int) {
+ h := dim / 2
+ canvas.Rect(0, 0, width, height, canvas.RGB(bg, bg, bg))
+ for size := grid; size < width; size += grid {
+ canvas.Ellipse(h, size, size/2, size/2, canvas.RGBA(fg, fg, fg, 0.2))
+ }
+}
+
+func main() {
+ canvas.Start(width, height)
+ canvas.Title("Funnel")
+ funnel(0, 255, 25, width)
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/gradient/gradient.go b/vendor/github.com/ajstarks/svgo/gradient/gradient.go
new file mode 100644
index 0000000..534321e
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/gradient/gradient.go
@@ -0,0 +1,58 @@
+// gradient shows sample gradient fills
+// +build !appengine
+
+package main
+
+import (
+ "os"
+ "strconv"
+
+ "github.com/ajstarks/svgo"
+)
+
+func main() {
+ width := 500
+ height := 500
+
+ lg := []svg.Offcolor{
+ {0, "rgb(255,255,0)", 1.0},
+ {100, "rgb(255,0,0)", .5},
+ {0, "rgb(200,200,200)", 0.0},
+ {100, "rgb(0,0,255)", 1.0}}
+
+ rainbow := []svg.Offcolor{
+ {10, "#00cc00", 1},
+ {30, "#006600", 1},
+ {70, "#cc0000", 1},
+ {90, "#000099", 1}}
+
+ rg := []svg.Offcolor{
+ {1, "powderblue", 1},
+ {10, "lightskyblue", 1},
+ {100, "darkblue", 1}}
+
+ g := svg.New(os.Stdout)
+ g.Start(width, height)
+ g.Title("Gradients")
+ g.Rect(0, 0, width, height, "fill:white")
+ g.Def()
+ g.LinearGradient("h", 0, 100, 0, 0, lg)
+ g.LinearGradient("v", 0, 0, 100, 0, lg)
+ g.LinearGradient("rainbow", 0, 0, 100, 0, rainbow)
+ g.RadialGradient("rad100", 50, 50, 100, 25, 25, rg)
+ g.RadialGradient("rad50", 50, 50, 50, 20, 50, rg)
+ for i := 50; i < 100; i += 10 {
+ g.RadialGradient("grad"+strconv.Itoa(i), 50, 50, uint8(i), 20, 50, rg)
+ }
+ g.DefEnd()
+
+ g.Ellipse(width/2, height/2, 100, 100, "fill:url(#rad100)")
+ g.Rect(300, 200, 100, 100, "fill:url(#h)")
+ g.Rect(100, 200, 100, 100, "fill:url(#v)")
+ g.Roundrect(10, 10, width-20, 50, 10, 10, "fill:url(#rainbow)")
+
+ for i := 50; i < 100; i += 10 {
+ g.Circle(i*5, 100, 15, "fill:url(#grad"+strconv.Itoa(i)+")")
+ }
+ g.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/html5logo/html5logo.go b/vendor/github.com/ajstarks/svgo/html5logo/html5logo.go
new file mode 100644
index 0000000..f532528
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/html5logo/html5logo.go
@@ -0,0 +1,41 @@
+// html5logo draws the w3c HTML5 logo, with scripting added
+// +build !appengine
+
+package main
+
+import (
+ "github.com/ajstarks/svgo"
+ "os"
+)
+
+func main() {
+ // HTML5 logo data from
+ // "Understanding and Optimizing Web Graphics", Session 508,
+ // Dean Jackson, Apple WWDC 2011
+ //
+ // Draggable elements via Jeff Schiller's dragsvg Javascript library
+
+ // shield
+ var sx = []int{71, 30, 481, 440, 255}
+ var sy = []int{460, 0, 0, 460, 512}
+ // highlight
+ var hx = []int{256, 405, 440, 256}
+ var hy = []int{472, 431, 37, 37}
+ // "five"
+ var fx = []int{181, 176, 392, 393, 396, 397, 114, 115, 129, 325, 318, 256, 192, 188, 132, 139, 256, 371, 372, 385, 387, 371}
+ var fy = []int{208, 150, 150, 138, 109, 94, 94, 109, 265, 265, 338, 355, 338, 293, 293, 382, 414, 382, 372, 223, 208, 208}
+
+ canvas := svg.New(os.Stdout)
+ width := 512
+ height := 512
+
+ // begin the document with the onload event, and namespace for dragging
+ canvas.Start(width, height, `onload="initializeDraggableElements();"`, `xmlns:drag="http://www.codedread.com/dragsvg"`)
+ canvas.Title("HTML5 Logo")
+ canvas.Rect(0, 0, width, height) // black background
+ canvas.Script("application/javascript", "http://www.codedread.com/dragsvg.js") // reference the drag script
+ canvas.Polygon(sx, sy, `drag:enable="true"`, canvas.RGB(227, 79, 38)) // draggable shield
+ canvas.Polygon(hx, hy, `drag:enable="true"`, canvas.RGBA(255, 255, 255, 0.3)) // draggable highlight
+ canvas.Polygon(fx, fy, `drag:enable="true"`, canvas.RGB(219, 219, 219)) // draggable five
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/imfade/imfade.go b/vendor/github.com/ajstarks/svgo/imfade/imfade.go
new file mode 100644
index 0000000..c00b82f
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/imfade/imfade.go
@@ -0,0 +1,32 @@
+// imfade progressively fades the Go gopher image
+// +build !appengine
+
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/ajstarks/svgo"
+)
+
+var canvas = svg.New(os.Stdout)
+
+func main() {
+ width := 768
+ height := 128
+ image := "gophercolor128x128.png"
+ if len(os.Args) > 1 {
+ image = os.Args[1]
+ }
+ canvas.Start(width, height)
+ canvas.Title("Image Fade")
+ opacity := 1.0
+ for i := 0; i < width-128; i += 100 {
+ canvas.Image(i, 0, 128, 128, image, fmt.Sprintf("opacity:%.2f", opacity))
+ opacity -= 0.10
+ }
+ canvas.Grid(0, 0, width, height, 16, "stroke:gray; opacity:0.2")
+
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/lewitt/lewitt.go b/vendor/github.com/ajstarks/svgo/lewitt/lewitt.go
new file mode 100644
index 0000000..fea90da
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/lewitt/lewitt.go
@@ -0,0 +1,84 @@
+// lewitt: inspired by by Sol LeWitt's Wall Drawing 91:
+// +build !appengine
+
+package main
+
+//
+// A six-inch (15 cm) grid covering the wall.
+// Within each square, not straight lines from side to side, using
+// red, yellow and blue pencils. Each square contains at least
+// one line of each color.
+//
+// This version violates the original instructions in that straight lines
+// as well as arcs are used
+
+import (
+ "flag"
+ "fmt"
+ "math/rand"
+ "os"
+ "time"
+
+ "github.com/ajstarks/svgo"
+)
+
+var canvas = svg.New(os.Stdout)
+
+const tilestyle = `stroke-width:1; stroke:rgb(128,128,128); stroke-opacity:0.5; fill:white`
+const penstyle = `stroke:rgb%s; fill:none; stroke-opacity:%.2f; stroke-width:%d`
+
+var width = 720
+var height = 720
+
+var nlines = flag.Int("n", 20, "number of lines/square")
+var nw = flag.Int("w", 3, "maximum pencil width")
+var pencils = []string{"(250, 13, 44)", "(247, 212, 70)", "(52, 114, 245)"}
+
+func background(v int) { canvas.Rect(0, 0, width, height, canvas.RGB(v, v, v)) }
+
+func lewitt(x int, y int, gsize int, n int, w int) {
+ var x1, x2, y1, y2 int
+ var op float64
+ canvas.Rect(x, y, gsize, gsize, tilestyle)
+ for i := 0; i < n; i++ {
+ choice := rand.Intn(len(pencils))
+ op = float64(random(1, 10)) / 10.0
+ x1 = random(x, x+gsize)
+ y1 = random(y, y+gsize)
+ x2 = random(x, x+gsize)
+ y2 = random(y, y+gsize)
+ if random(0, 100) > 50 {
+ canvas.Line(x1, y1, x2, y2, fmt.Sprintf(penstyle, pencils[choice], op, random(1, w)))
+ } else {
+ canvas.Arc(x1, y1, gsize, gsize, 0, false, true, x2, y2, fmt.Sprintf(penstyle, pencils[choice], op, random(1, w)))
+ }
+ }
+}
+
+func random(howsmall, howbig int) int {
+ if howsmall >= howbig {
+ return howsmall
+ }
+ return rand.Intn(howbig-howsmall) + howsmall
+}
+
+func init() {
+ flag.Parse()
+ rand.Seed(int64(time.Now().Nanosecond()) % 1e9)
+}
+
+func main() {
+
+ canvas.Start(width, height)
+ canvas.Title("Sol Lewitt's Wall Drawing 91")
+ background(255)
+ gsize := 120
+ nc := width / gsize
+ nr := height / gsize
+ for cols := 0; cols < nc; cols++ {
+ for rows := 0; rows < nr; rows++ {
+ lewitt(cols*gsize, rows*gsize, gsize, *nlines, *nw)
+ }
+ }
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/ltr/ltr.go b/vendor/github.com/ajstarks/svgo/ltr/ltr.go
new file mode 100644
index 0000000..d58a9e1
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/ltr/ltr.go
@@ -0,0 +1,178 @@
+// ltr: Layer Tennis remixes
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+
+ "github.com/ajstarks/svgo"
+)
+
+var (
+ canvas = svg.New(os.Stdout)
+ poster, opacity, row, col, offset bool
+ title string
+ width, height int
+)
+
+const (
+ stdwidth = 900
+ stdheight = 280
+ ni = 11
+)
+
+// imagefiles returns a list of files in the specifed directory
+// or nil on error. Each file includes the prepended directory name
+func imagefiles(directory string) []string {
+ f, ferr := os.Open(directory)
+ if ferr != nil {
+ return nil
+ }
+ defer f.Close()
+ files, derr := f.Readdir(-1)
+ if derr != nil || len(files) == 0 {
+ return nil
+ }
+ names := make([]string, len(files))
+ for i, v := range files {
+ names[i] = directory + "/" + v.Name()
+ }
+ return names
+}
+
+// ltposter creates poster style: a title, followed by a list
+// of volleys
+func ltposter(x, y, w, h int, f []string) {
+ canvas.Image(x, y, w*2, h*2, f[0]) // first file, assumed to be the banner
+ y = y + (h * 2)
+ for i := 1; i < len(f); i += 2 {
+ canvas.Image(x, y, w, h, f[i])
+ canvas.Image(x+w, y, w, h, f[i+1])
+ if i%2 == 1 {
+ y += h
+ }
+ }
+}
+
+// ltcol creates a single column of volley images
+func ltcol(x, y, w, h int, f []string) {
+ for i := 0; i < len(f); i++ {
+ canvas.Image(x, y, w, h, f[i])
+ y += h
+ }
+}
+
+// ltop creates a view with each volley stacked together with
+// semi-transparent opacity
+func ltop(x, y, w, h int, f []string) {
+ for i := 1; i < len(f); i++ { // skip the first file, assumed to be the banner
+ canvas.Image(x, y, w, h, f[i], "opacity:0.2")
+ }
+}
+
+// ltrow creates a row-wise view of volley images.
+func ltrow(x, y, w, h int, f []string) {
+ for i := 0; i < len(f); i++ {
+ canvas.Image(x, y, w, h, f[i])
+ x += w
+ }
+}
+
+// ltoffset creates a view where each volley is offset from its opposing volley.
+func ltoffset(x, y, w, h int, f []string) {
+ for i := 1; i < len(f); i++ { // skip the first file, assumed to be the banner
+
+ if i%2 == 0 {
+ x += w
+ } else {
+ x = 0
+ }
+ canvas.Image(x, y, w, h, f[i])
+ y += h
+ }
+}
+
+// dotitle creates the title
+func dotitle(s string) {
+ if len(title) > 0 {
+ canvas.Title(title)
+ } else {
+ canvas.Title(s)
+ }
+}
+
+// init sets up the command line flags.
+func init() {
+ flag.BoolVar(&poster, "poster", false, "poster style")
+ flag.BoolVar(&opacity, "opacity", false, "opacity style")
+ flag.BoolVar(&row, "row", false, "display is a single row")
+ flag.BoolVar(&col, "col", false, "display in a single column")
+ flag.BoolVar(&offset, "offset", false, "display in a row, even layers offset")
+ flag.IntVar(&width, "width", stdwidth, "image width")
+ flag.IntVar(&height, "height", stdheight, "image height")
+ flag.StringVar(&title, "title", "", "title")
+ flag.Parse()
+}
+
+func main() {
+ x := 0
+ y := 0
+ nd := len(flag.Args())
+ for i, dir := range flag.Args() {
+ filelist := imagefiles(dir)
+ if len(filelist) != ni || filelist == nil {
+ fmt.Fprintf(os.Stderr, "in the %s directory, need %d images, read %d\n", dir, ni, len(filelist))
+ continue
+ }
+ switch {
+
+ case opacity:
+ if i == 0 {
+ canvas.Start(width*nd, height*nd)
+ dotitle(dir)
+ }
+ ltop(x, y, width, height, filelist)
+ y += height
+
+ case poster:
+ if i == 0 {
+ canvas.Start(width, ((height*(ni-1)/4)+height)*nd)
+ dotitle(dir)
+ }
+ ltposter(x, y, width/2, height/2, filelist)
+ y += (height * 3) + (height / 2)
+
+ case col:
+ if i == 0 {
+ canvas.Start(width*nd, height*ni)
+ dotitle(dir)
+ }
+ ltcol(x, y, width, height, filelist)
+ x += width
+
+ case row:
+ if i == 0 {
+ canvas.Start(width*ni, height*nd)
+ dotitle(dir)
+ }
+ ltrow(x, y, width, height, filelist)
+ y += height
+
+ case offset:
+ n := ni - 1
+ pw := width * 2
+ ph := nd * (height * (n))
+ if i == 0 {
+ canvas.Start(pw, ph)
+ canvas.Rect(0, 0, pw, ph, "fill:white")
+ dotitle(dir)
+ }
+ ltoffset(x, y, width, height, filelist)
+ y += n * height
+
+ }
+ }
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/marker/marker.go b/vendor/github.com/ajstarks/svgo/marker/marker.go
new file mode 100644
index 0000000..bb7b642
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/marker/marker.go
@@ -0,0 +1,39 @@
+// marker test
+// +build !appengine
+
+package main
+
+import (
+ "github.com/ajstarks/svgo"
+ "os"
+)
+
+func main() {
+ canvas := svg.New(os.Stdout)
+ canvas.Start(500, 500)
+ canvas.Title("Marker")
+
+ canvas.Def()
+ canvas.Marker("dot", 5, 5, 8, 8)
+ canvas.Circle(5, 5, 3, "fill:black")
+ canvas.MarkerEnd()
+
+ canvas.Marker("box", 5, 5, 8, 8)
+ canvas.CenterRect(5, 5, 6, 6, "fill:green")
+ canvas.MarkerEnd()
+
+ canvas.Marker("arrow", 2, 6, 13, 13)
+ canvas.Path("M2,2 L2,11 L10,6 L2,2", "fill:blue")
+ canvas.MarkerEnd()
+ canvas.DefEnd()
+
+ x := []int{100, 250, 100}
+ y := []int{100, 250, 400}
+ canvas.Polyline(x, y,
+ `fill="none"`,
+ `stroke="red"`,
+ `marker-start="url(#dot)"`,
+ `marker-mid="url(#arrow)"`,
+ `marker-end="url(#box)"`)
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/newsvg b/vendor/github.com/ajstarks/svgo/newsvg
new file mode 100644
index 0000000..46c7099
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/newsvg
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+if test $# -lt 1
+then
+ echo "specify a file"
+ exit 2
+fi
+
+if test ! -f $1
+then
+cat < $1
+package main
+
+import (
+ "github.com/ajstarks/svgo"
+ "os"
+)
+
+var (
+ width = 500
+ height = 500
+ canvas = svg.New(os.Stdout)
+)
+
+func background(v int) { canvas.Rect(0, 0, width, height, canvas.RGB(v, v, v)) }
+
+
+func main() {
+ canvas.Start(width, height)
+ background(255)
+
+ // your code here
+
+ canvas.Grid(0, 0, width, height, 10, "stroke:black;opacity:0.1")
+ canvas.End()
+}
+!
+fi
+$EDITOR $1
diff --git a/vendor/github.com/ajstarks/svgo/paths/paths.go b/vendor/github.com/ajstarks/svgo/paths/paths.go
new file mode 100644
index 0000000..5a9283a
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/paths/paths.go
@@ -0,0 +1,31 @@
+// paths draws the W3C logo as a paths
+// +build !appengine
+
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/ajstarks/svgo"
+)
+
+var canvas = svg.New(os.Stdout)
+
+func w3c() {
+ w3path := `M36,5l12,41l12-41h33v4l-13,21c30,10,2,69-21,28l7-2c15,27,33,-22,3,-19v-4l12-20h-15l-17,59h-1l-13-42l-12,42h-1l-20-67h9l12,41l8-28l-4-13h9`
+ cpath := `M94,53c15,32,30,14,35,7l-1-7c-16,26-32,3-34,0M122,16c-10-21-34,0-21,30c-5-30 16,-38 23,-21l5-10l-2-9`
+ canvas.Path(w3path, "fill:#005A9C")
+ canvas.Path(cpath)
+}
+
+func main() {
+ canvas.Startview(700, 200, 0, 0, 700, 200)
+ canvas.Title("Paths")
+ for i := 0; i < 5; i++ {
+ canvas.Gtransform(fmt.Sprintf("translate(%d,0)", i*130))
+ w3c()
+ canvas.Gend()
+ }
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/pattern/pattern.go b/vendor/github.com/ajstarks/svgo/pattern/pattern.go
new file mode 100644
index 0000000..0545683
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/pattern/pattern.go
@@ -0,0 +1,33 @@
+// pattern: test the pattern function
+package main
+
+import (
+ "fmt"
+ "github.com/ajstarks/svgo"
+ "os"
+)
+
+func main() {
+ canvas := svg.New(os.Stdout)
+ w, h := 500, 500
+ pct := 5
+ pw, ph := (w*pct)/100, (h*pct)/100
+ canvas.Start(w, h)
+
+ // define the pattern
+ canvas.Def()
+ canvas.Pattern("hatch", 0, 0, pw, ph, "user")
+ canvas.Gstyle("fill:none;stroke-width:1")
+ canvas.Path(fmt.Sprintf("M0,0 l%d,%d", pw, ph), "stroke:red")
+ canvas.Path(fmt.Sprintf("M%d,0 l-%d,%d", pw, pw, ph), "stroke:blue")
+ canvas.Gend()
+ canvas.PatternEnd()
+ canvas.DefEnd()
+
+ // use the pattern
+ canvas.Gstyle("stroke:black; stroke-width:2")
+ canvas.Circle(w/2, h/2, h/8, "fill:url(#hatch)")
+ canvas.CenterRect((w*4)/5, h/2, h/4, h/4, "fill:url(#hatch)")
+ canvas.Gend()
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/picserv/index.go b/vendor/github.com/ajstarks/svgo/picserv/index.go
new file mode 100644
index 0000000..9feb426
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/picserv/index.go
@@ -0,0 +1,127 @@
+package main
+const (
+index = `
+
+ pic256
+
+
+
+ Pic256
+ Programmed pictures in a 256x256 square
+ Defaults
+
+
rotext
+
flower
+
cube
+
funnel
+
rshape
+
lewitt
+
mondrian
+
face
+
clock
+
pacman
+
tux
+
ubuntu
+
+ Variations
+
+ rotext
+
+
char=a
+
char=b&ti=40
+
char=c&ti=60
+
char=d&ti=90&font=Courier
+
+
+ flower
+
+
petals=10&n=100
+
petals=15&n=50
+
petals=30&n=20
+
petals=30&n=10
+
+
+ cube
+
+
y=80&row=1
+
y=50&row=2
+
&row=3
+
y=0&row=4
+
+
+ funnel
+
+
step=10
+
step=15
+
step=25
+
step=25&bg=white&fg=black
+
+
+ rshape
+
+
shape=c
+
shape=r
+
same=f
+
shape=r&same=t
+
+
+ lewitt
+
+
pen=1&lines=20
+
pen=2&lines=30
+
pen=3&lines=40
+
pen=5&?lines=100
+
+
+
+ mondrian
+
+
random=true
+
random=t
+
random=1
+
random=f
+
+
+ face
+
+
mood=happy&glance=u
+
mood=neutral&glance=d
+
mood=sad&glance=l
+
mood=happy&glance=r
+
+
+ clock
+
+
clock
+
hour=23
+
hour=12&min=34
+
hour=6&min=30&sec=0
+
+
+ pacman
+
+
pacman
+
angle=10
+
angle=40
+
angle=60
+
+
+
+
+`)
diff --git a/vendor/github.com/ajstarks/svgo/picserv/pic256.html b/vendor/github.com/ajstarks/svgo/picserv/pic256.html
new file mode 100644
index 0000000..6873184
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/picserv/pic256.html
@@ -0,0 +1,124 @@
+
+
+ pic256
+
+
+
+ Pic256
+ Programmed pictures in a 256x256 square
+ Defaults
+
+
rotext
+
flower
+
cube
+
funnel
+
rshape
+
lewitt
+
mondrian
+
face
+
clock
+
pacman
+
tux
+
ubuntu
+
+ Variations
+
+ rotext
+
+
char=a
+
char=b&ti=40
+
char=c&ti=60
+
char=d&ti=90&font=Courier
+
+
+ flower
+
+
petals=10&n=100
+
petals=15&n=50
+
petals=30&n=20
+
petals=30&n=10
+
+
+ cube
+
+
y=80&row=1
+
y=50&row=2
+
&row=3
+
y=0&row=4
+
+
+ funnel
+
+
step=10
+
step=15
+
step=25
+
step=25&bg=white&fg=black
+
+
+ rshape
+
+
shape=c
+
shape=r
+
same=f
+
shape=r&same=t
+
+
+ lewitt
+
+
pen=1&lines=20
+
pen=2&lines=30
+
pen=3&lines=40
+
pen=5&?lines=100
+
+
+
+ mondrian
+
+
random=true
+
random=t
+
random=1
+
random=f
+
+
+ face
+
+
mood=happy&glance=u
+
mood=neutral&glance=d
+
mood=sad&glance=l
+
mood=happy&glance=r
+
+
+ clock
+
+
clock
+
hour=23
+
hour=12&min=34
+
hour=6&min=30&sec=0
+
+
+ pacman
+
+
pacman
+
angle=10
+
angle=40
+
angle=60
+
+
+
+
diff --git a/vendor/github.com/ajstarks/svgo/picserv/picserv.go b/vendor/github.com/ajstarks/svgo/picserv/picserv.go
new file mode 100644
index 0000000..48970cd
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/picserv/picserv.go
@@ -0,0 +1,672 @@
+// picserv: serve pictures
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "math"
+ "math/rand"
+ "net/http"
+ "net/url"
+ "strconv"
+ "time"
+
+ "github.com/ajstarks/svgo"
+)
+
+var listen = flag.String("listen", ":1958", "http service address")
+
+const (
+ arcstyle = "stroke:red;stroke-linecap:round;fill:none;stroke-width:10"
+ rotextfmt = "fill:%s;font-family:%s;font-size:%dpt"
+ flowerfmt = "stroke:rgb(%d,%d,%d); stroke-opacity:%.2f; stroke-width:%d"
+ tilestyle = "stroke-width:1; stroke:rgb(128,128,128); stroke-opacity:0.5; fill:white"
+ penstyle = "stroke:rgb%s; fill:none; stroke-opacity:%.2f; stroke-width:%d"
+ width = 256
+ height = 256
+)
+
+// include index
+//go:generate ih -v index -o index.go pic256.html
+
+// init seeds the RNG
+func init() {
+ rand.Seed(time.Now().Unix() % 1e9)
+}
+
+// serve stuff
+func main() {
+ flag.Parse()
+ http.Handle("/", http.HandlerFunc(picindex))
+ http.Handle("/index/", http.HandlerFunc(picindex))
+ http.Handle("/pic256.html", http.HandlerFunc(picindex))
+ http.Handle("/rotext/", http.HandlerFunc(rotext))
+ http.Handle("/rshape/", http.HandlerFunc(rshape))
+ http.Handle("/face/", http.HandlerFunc(face))
+ http.Handle("/flower/", http.HandlerFunc(flower))
+ http.Handle("/cube/", http.HandlerFunc(cube))
+ http.Handle("/lewitt/", http.HandlerFunc(lewitt))
+ http.Handle("/mondrian/", http.HandlerFunc(mondrian))
+ http.Handle("/funnel/", http.HandlerFunc(funnel))
+ http.Handle("/clock/", http.HandlerFunc(clock))
+ http.Handle("/pacman/", http.HandlerFunc(pacman))
+ http.Handle("/ubuntu/", http.HandlerFunc(ubuntu))
+ http.Handle("/tux/", http.HandlerFunc(tux))
+ log.Printf("listen on %s", *listen)
+ err := http.ListenAndServe(*listen, nil)
+ if err != nil {
+ log.Fatal("ListenAndServe:", err)
+ }
+}
+
+// qstring returns the string value of the query string
+func qstring(q url.Values, key, defval string, length int) string {
+ var retval string
+ p, ok := q[key]
+ if ok {
+ retval = p[0]
+ } else {
+ return defval
+ }
+ if len(retval) > length {
+ return retval[:length]
+ }
+ return retval
+}
+
+// qfloat returns the float64 value of a query string, within limits
+func qfloat(q url.Values, key string, defval float64, min, max float64) float64 {
+ var retval float64
+ var err error
+ p, ok := q[key]
+ if ok {
+ retval, err = strconv.ParseFloat(p[0], 64)
+ if err != nil {
+ return defval
+ }
+ } else {
+ return defval
+ }
+ if retval < min || retval > max {
+ return defval
+ }
+ return retval
+}
+
+// qfint returns the integer value of a query string, within limits
+func qint(q url.Values, key string, defval int, min, max int) int {
+ var retval int
+ var err error
+ p, ok := q[key]
+ if ok {
+ retval, err = strconv.Atoi(p[0])
+ if err != nil {
+ return defval
+ }
+ } else {
+ return defval
+ }
+ if retval < min || retval > max {
+ return defval
+ }
+ return retval
+}
+
+// qbool returns the boolean value of a query string
+func qbool(q url.Values, key string, defval bool) bool {
+ p, ok := q[key]
+ if ok {
+ switch p[0] {
+ case "t", "true", "T", "1", "on":
+ return true
+ case "f", "false", "F", "0", "off":
+ return false
+ default:
+ return defval
+ }
+ } else {
+ return defval
+ }
+}
+
+func random(howsmall, howbig int) int {
+ if howsmall >= howbig {
+ return howsmall
+ }
+ return rand.Intn(howbig-howsmall) + howsmall
+}
+
+func randcolor() string {
+ return fmt.Sprintf("fill:rgb(%d,%d,%d)", rand.Intn(255), rand.Intn(255), rand.Intn(255))
+}
+
+// picindex shows an HTML document that describes the service
+// The "index" variable is a string that holds the document,
+// made with go generate
+func picindex(w http.ResponseWriter, req *http.Request) {
+ log.Printf("index: %s %s %s", req.RemoteAddr, req.URL.Path, req.UserAgent())
+ io.WriteString(w, index)
+}
+
+// rotext makes rotated and faded text
+func rotext(w http.ResponseWriter, req *http.Request) {
+
+ log.Printf("rotext: %s", req.RemoteAddr)
+ query := req.URL.Query()
+
+ rchar := qstring(query, "char", "a", 3) // the string
+ ti := qfloat(query, "ti", 10, 5, 360) // angle interval
+ bg := qstring(query, "bg", "black", 20) // background color
+ fg := qstring(query, "fg", "white", 20) // text color
+ font := qstring(query, "font", "serif", 50) // font name
+ a, ai := 1.0, 0.03
+
+ w.Header().Set("Content-type", "image/svg+xml")
+ canvas := svg.New(w)
+ canvas.Start(width, height)
+ canvas.Title("Rotated Text")
+ canvas.Rect(0, 0, width, height, "fill:"+bg)
+ canvas.Gstyle(fmt.Sprintf(rotextfmt, fg, font, width/(len(rchar)+1)))
+ for t := 0.0; t <= 360.0; t += ti {
+ canvas.TranslateRotate(width/2, height/2, t)
+ canvas.Text(0, 0, rchar, fmt.Sprintf("fill-opacity:%.2f", a))
+ canvas.Gend()
+ a -= ai
+ }
+ canvas.Gend()
+ canvas.End()
+}
+
+// face draws a face, with mood (happy, sad, neutral),
+// and glance (up, down, left, right, middle)
+func face(w http.ResponseWriter, req *http.Request) {
+
+ log.Printf("face: %s", req.RemoteAddr)
+ query := req.URL.Query()
+
+ mood := qstring(query, "mood", "h", 10)
+ glance := qstring(query, "glance", "m", 10)
+ ex1 := width / 4 // left eye x 25% from the left
+ ex2 := (width * 3) / 4 // right eye x 25% from the right
+ ey := height / 3 // eye y one third from the bottom
+ sy := (height * 2) / 3 // mouth y two-thirds from the bottom
+ er := width / 12 // eye radius
+ ax := height / 3 // mouth arc x
+ ay := height / 3 // mounth arc y
+ aflag := false
+ pupilsize := er / 3
+ xoffset := 0
+ yoffset := 0
+
+ // adjust mouth according to mood
+ switch mood {
+ case "n", "neutral":
+ ay = 0
+ case "s", "sad":
+ sy = (height * 4) / 5
+ aflag = true
+ }
+
+ // adjust pupils according to glance
+ switch glance {
+ case "l", "left":
+ xoffset = -pupilsize
+ case "r", "right":
+ xoffset = pupilsize
+ case "d", "down":
+ yoffset = pupilsize
+ case "u", "up":
+ yoffset = -pupilsize
+ }
+
+ w.Header().Set("Content-type", "image/svg+xml")
+ canvas := svg.New(w)
+ canvas.Start(width, height)
+ canvas.Title("Face")
+ canvas.Rect(0, 0, width, height, "fill:white") // background
+ canvas.Circle(ex1, ey, er) // lefteye
+ canvas.Circle(ex2, ey, er) // righteye
+ canvas.Circle(ex1+xoffset, ey+yoffset, pupilsize, "fill:white") // left pupil
+ canvas.Circle(ex2+xoffset, ey+yoffset, pupilsize, "fill:white") // right pupil
+ canvas.Arc(ex1, sy, ax, ay, 0, false, aflag, ex2, sy, arcstyle) // mouth
+ canvas.End()
+}
+
+// rshape draws random shapes
+func rshape(w http.ResponseWriter, req *http.Request) {
+
+ log.Printf("rshape: %s", req.RemoteAddr)
+ query := req.URL.Query()
+
+ n := qint(query, "n", 150, 5, 200) // number of shapes
+ shape := qstring(query, "shape", "c", 10) // type of shape
+ bg := qstring(query, "bg", "white", 20) // background color
+ samesize := qbool(query, "same", false) // regular or oblong
+ canvas := svg.New(w)
+
+ // draw rect, square, ellipse or circle according to the specified shape
+ shapefunc := canvas.Ellipse
+ switch shape {
+ case "r", "box":
+ shapefunc = canvas.Rect
+ samesize = false
+ case "s", "sq", "square":
+ shapefunc = canvas.Rect
+ samesize = true
+ case "e", "ellipse":
+ shapefunc = canvas.Ellipse
+ samesize = false
+ case "c", "circle", "dot":
+ shapefunc = canvas.Ellipse
+ samesize = true
+ }
+
+ w.Header().Set("Content-type", "image/svg+xml")
+ var s1, s2 int
+ canvas.Start(width, height)
+ canvas.Title("Random Shapes")
+ canvas.Rect(0, 0, width, height, "fill:"+bg)
+ for i := 0; i < n; i++ {
+ s1 = rand.Intn(width / 5)
+ if samesize {
+ s2 = s1
+ } else {
+ s2 = rand.Intn(height / 5)
+ }
+ shapefunc(rand.Intn(width), rand.Intn(height), s1, s2,
+ fmt.Sprintf("fill-opacity:%.2f;fill:rgb(%d,%d,%d)",
+ rand.Float64(), rand.Intn(256), rand.Intn(256), rand.Intn(256)))
+ }
+ canvas.End()
+}
+
+func flower(w http.ResponseWriter, req *http.Request) {
+
+ log.Printf("flower: %s", req.RemoteAddr)
+ query := req.URL.Query()
+
+ n := qint(query, "n", 200, 10, 200) // number of "flowers"
+ np := qint(query, "petals", 15, 10, 60) // number of "petals" per flower
+ opacity := qint(query, "op", 50, 20, 100) // opacity
+ psize := qint(query, "size", 30, 5, 50) // length of the petals
+ thickness := qint(query, "thick", 10, 3, 20) // petal thickness
+
+ limit := 2.0 * math.Pi
+
+ w.Header().Set("Content-type", "image/svg+xml")
+ canvas := svg.New(w)
+ canvas.Start(width, height)
+ canvas.Title("Flowers")
+ canvas.Rect(0, 0, width, height, "fill:white")
+
+ for i := 0; i < n; i++ {
+ x := rand.Intn(width)
+ y := rand.Intn(height)
+ r := float64(random(10, psize))
+
+ canvas.Gstyle(fmt.Sprintf(flowerfmt, rand.Intn(255), rand.Intn(255), rand.Intn(255),
+ float64(random(10, opacity))/100.0, random(2, thickness)))
+ for theta := 0.0; theta < limit; theta += limit / float64(random(10, np)) {
+ xr := r * math.Cos(theta)
+ yr := r * math.Sin(theta)
+ canvas.Line(x, y, x+int(xr), y+int(yr))
+ }
+ canvas.Gend()
+ }
+ canvas.End()
+}
+
+// rcube makes a cube with three visible faces, each with a random color
+func rcube(canvas *svg.SVG, x, y, l int) {
+
+ // top face
+ tx := []int{x, x + (l * 3), x, x - (l * 3), x}
+ ty := []int{y, y + (l * 2), y + (l * 4), y + (l * 2), y}
+
+ // left face
+ lx := []int{x - (l * 3), x, x, x - (l * 3), x - (l * 3)}
+ ly := []int{y + (l * 2), y + (l * 4), y + (l * 8), y + (l * 6), y + (l * 2)}
+
+ // right face
+ rx := []int{x + (l * 3), x + (l * 3), x, x, x + (l * 3)}
+ ry := []int{y + (l * 2), y + (l * 6), y + (l * 8), y + (l * 4), y + (l * 2)}
+
+ canvas.Polygon(tx, ty, randcolor())
+ canvas.Polygon(lx, ly, randcolor())
+ canvas.Polygon(rx, ry, randcolor())
+}
+
+// cube draws a grid of cubes, n rows deep.
+// The grid begins at (xp, yp), with hspace between cubes in a row, and vspace between rows.
+func cube(w http.ResponseWriter, req *http.Request) {
+
+ log.Printf("cube: %s", req.RemoteAddr)
+ query := req.URL.Query()
+
+ bgcolor := qstring(query, "bg", randcolor(), 30) // background color
+ n := qint(query, "row", 3, 1, 20) // number of rows
+ hspace := qint(query, "hs", width/5, 0, width) // horizontal space
+ vspace := qint(query, "vs", height/4, 0, height) // vertical space
+ size := qint(query, "size", width/30, 2, width/4) // cube size
+ xp := qint(query, "x", width/10, 0, width/2) // initial x position
+ yp := qint(query, "y", height/10, 0, height/2) // initial y position
+
+ w.Header().Set("Content-type", "image/svg+xml")
+ canvas := svg.New(w)
+ canvas.Start(width, height)
+ canvas.Title("Cubes")
+ canvas.Rect(0, 0, width, height, bgcolor)
+ y := yp
+ for r := 0; r < n; r++ {
+ for x := xp; x < width; x += hspace {
+ rcube(canvas, x, y, size)
+ }
+ y += vspace
+ }
+ canvas.End()
+}
+
+var pencils = []string{"(250, 13, 44)", "(247, 212, 70)", "(52, 114, 245)"}
+
+func lew(canvas *svg.SVG, x int, y int, gsize int, n int, w int) {
+ var x1, x2, y1, y2 int
+ var op float64
+ canvas.Rect(x, y, gsize, gsize, tilestyle)
+ for i := 0; i < n; i++ {
+ choice := rand.Intn(len(pencils))
+ op = float64(random(1, 10)) / 10.0
+ x1 = random(x, x+gsize)
+ y1 = random(y, y+gsize)
+ x2 = random(x, x+gsize)
+ y2 = random(y, y+gsize)
+ if random(0, 100) > 50 {
+ canvas.Line(x1, y1, x2, y2, fmt.Sprintf(penstyle, pencils[choice], op, random(1, w)))
+ } else {
+ canvas.Arc(x1, y1, gsize, gsize, 0, false, true, x2, y2, fmt.Sprintf(penstyle, pencils[choice], op, random(1, w)))
+ }
+ }
+}
+
+// lewitt simulates Sol Lewitt's Wall Drawing 91
+func lewitt(w http.ResponseWriter, req *http.Request) {
+ query := req.URL.Query()
+ log.Printf("lewitt: %s", req.RemoteAddr)
+
+ nlines := qint(query, "lines", 20, 5, 100)
+ nw := qint(query, "pen", 3, 1, 5)
+
+ w.Header().Set("Content-type", "image/svg+xml")
+ canvas := svg.New(w)
+ canvas.Start(width, height)
+ canvas.Title("Sol Lewitt's Wall Drawing 91")
+ canvas.Rect(0, 0, width, height, "fill:white")
+ gsize := width / 6
+ nc := width / gsize
+ nr := height / gsize
+ for cols := 0; cols < nc; cols++ {
+ for rows := 0; rows < nr; rows++ {
+ lew(canvas, cols*gsize, rows*gsize, gsize, nlines, nw)
+ }
+ }
+ canvas.End()
+}
+
+// pmcolor returns a random color from Mondrian's set, or a specified standard color
+func pmcolor(randcolor bool, standard string) string {
+ moncolors := []string{"white", "red", "blue", "yellow"}
+ if randcolor {
+ return moncolors[rand.Intn(10000)%4]
+ }
+ return standard
+}
+
+// mondrian draws a view inspired by Piet Mondrian's Composition red, blue, white and yellow
+func mondrian(w http.ResponseWriter, req *http.Request) {
+ log.Printf("mondrian: %s", req.RemoteAddr)
+ query := req.URL.Query()
+ rc := qbool(query, "random", false)
+ w3 := width / 3
+ w6 := w3 / 2
+ w23 := w3 * 2
+
+ w.Header().Set("Content-type", "image/svg+xml")
+ canvas := svg.New(w)
+ canvas.Start(width, height)
+ canvas.Title("Mondrian Composition in red, blue, white and yellow")
+ canvas.Gstyle("stroke:black;stroke-width:6")
+ canvas.Rect(0, 0, w3, w3, "fill:"+pmcolor(rc, "white"))
+ canvas.Rect(0, w3, w3, w3, "fill:"+pmcolor(rc, "white"))
+ canvas.Rect(0, w23, w3, w3, "fill:"+pmcolor(rc, "blue"))
+ canvas.Rect(w3, 0, w23, w23, "fill:"+pmcolor(rc, "red"))
+ canvas.Rect(w3, w23, w23, w3, "fill:"+pmcolor(rc, "white"))
+ canvas.Rect(width-w6, height-w3, w3-w6, w6, "fill:"+pmcolor(rc, "white"))
+ canvas.Rect(width-w6, height-w6, w3-w6, w6, "fill:"+pmcolor(rc, "yellow"))
+ canvas.Gend()
+ canvas.Rect(0, 0, width, height, "fill:none;stroke:black;stroke-width:12")
+ canvas.End()
+}
+
+// funnel makes a funnel from fading ellipses
+func funnel(w http.ResponseWriter, req *http.Request) {
+ log.Printf("funnel: %s", req.RemoteAddr)
+ query := req.URL.Query()
+ bg := qstring(query, "bg", "black", 20)
+ fg := qstring(query, "fg", "white", 20)
+ grid := qint(query, "step", 25, 10, height/3)
+ h := width / 2
+
+ w.Header().Set("Content-type", "image/svg+xml")
+ canvas := svg.New(w)
+ canvas.Start(width, height)
+ canvas.Title("Funnel")
+ canvas.Rect(0, 0, width, height, "fill:"+bg)
+ canvas.Gstyle("fill-opacity:0.2;fill:" + fg)
+ for size := grid; size < width; size += grid {
+ canvas.Ellipse(h, size, size/2, size/2)
+ }
+ canvas.Gend()
+ canvas.End()
+}
+
+var (
+ digits = [12]string{"12", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"}
+ hrangles = [12]float64{90, 60, 30, 0, 330, 300, 270, 240, 210, 180, 150, 120}
+ minangles = [60]float64{
+ 90, 84, 78, 72, 66, 60, 54, 48, 42, 36, 30, 24, 18, 12, 6,
+ 0, 354, 348, 342, 336, 330, 324, 318, 312, 306,
+ 300, 294, 288, 282, 276, 270, 264, 258, 252, 246, 240, 234, 228, 222, 216,
+ 210, 204, 198, 192, 186, 180, 174, 168, 162, 156,
+ 150, 144, 138, 132, 126, 120, 114, 108, 102, 96,
+ }
+)
+
+const (
+ radians = math.Pi / 180.0
+ hrcolor = "rgb(127,0,0)"
+ secolor = "rgb(0,0,255)"
+ mincolor = "rgb(127,127,127)"
+ bgcolor = "rgb(140,140,140)"
+ linefmt = "stroke:%s;stroke-width:%d"
+ digitfmt = "font-family:Helvetica,Calibri,sans-serif;text-anchor:middle;font-size:%dpx"
+)
+
+// clock draws an analog clock
+func clock(w http.ResponseWriter, req *http.Request) {
+ log.Printf("clock: %s", req.RemoteAddr)
+ query := req.URL.Query()
+ size := width / 3
+ basesize := size / 12
+ fs := (size * 2) + (size / 2)
+
+ w.Header().Set("Content-type", "image/svg+xml")
+ canvas := svg.New(w)
+ canvas.Start(width, height)
+
+ // clock face
+ cx, cy := width/2, height/2
+ now := time.Now()
+ hour, min, sec := now.Hour(), now.Minute(), now.Second()
+
+ hour = qint(query, "hour", hour, 0, 23)
+ min = qint(query, "min", min, 0, 59)
+ sec = qint(query, "sec", sec, 0, 59)
+
+ canvas.Rect(0, 0, width, height, "fill:black")
+ canvas.Roundrect(cx-(fs/2), cy-(fs/2), fs, fs, basesize, basesize, "fill:"+bgcolor)
+ canvas.Circle(cx, cy, size+(size/6), "fill:white")
+ canvas.Gstyle(fmt.Sprintf(digitfmt, basesize*2))
+
+ // draw the clock-face digits
+ r := float64(size)
+ rx := float64(cx)
+ ry := float64(cy)
+ for h := 12; h > 0; h-- {
+ t := hrangles[h%12] * radians
+ x := rx + r*math.Cos(t)
+ y := ry + r*math.Sin(t)
+ canvas.Text(int(x), height-int(y), digits[h%12], "baseline-shift:-30%")
+ }
+ canvas.Gend()
+
+ // hour hand: special case: if the minute is greater than 30,
+ // adjust the hour hand the move proportionally closer to the upcoming hour.
+ t := hrangles[hour%12]
+ if min > 30 {
+ t = t - (30.0 * (float64(min) / 60.0))
+ }
+ hr := r * 0.6
+ hx := rx + hr*math.Cos(t*radians)
+ hy := ry + hr*math.Sin(t*radians)
+
+ // minute hand
+ mr := r * 0.9
+ t = minangles[min] * radians
+ mx := rx + mr*math.Cos(t)
+ my := ry + mr*math.Sin(t)
+
+ // second hand
+ sr := r
+ t = minangles[sec] * radians
+ sx := rx + sr*math.Cos(t)
+ sy := ry + sr*math.Sin(t)
+
+ // draw the hands and center dot
+ canvas.Line(cx, cy, int(hx), height-int(hy), fmt.Sprintf(linefmt, hrcolor, basesize))
+ canvas.Line(cx, cy, int(mx), height-int(my), fmt.Sprintf(linefmt, mincolor, basesize/2))
+ canvas.Line(cx, cy, int(sx), height-int(sy), fmt.Sprintf(linefmt, secolor, basesize/4))
+ canvas.Circle(cx, cy, basesize, "fill:black")
+ canvas.End()
+}
+
+// pacman draws pacman with dots
+func pacman(w http.ResponseWriter, req *http.Request) {
+ log.Printf("pacman: %s", req.RemoteAddr)
+ query := req.URL.Query()
+ angle := qfloat(query, "angle", 30.0, 10.0, 70.0)
+
+ w.Header().Set("Content-type", "image/svg+xml")
+ canvas := svg.New(w)
+ cx, cy := width/2, height/2
+ r := width / 5
+ p := r / 8
+ canvas.Start(width, height)
+ canvas.Rect(0, 0, width, height)
+
+ // draw dots
+ canvas.Gstyle("fill:white")
+ for x := 0; x < 100; x += 12 {
+ if x < 50 {
+ canvas.Circle((width*x)/100, cy, p, "fill-opacity:0.5")
+ } else {
+ canvas.Circle((width*x)/100, cy, p, "fill-opacity:1")
+ }
+ }
+ canvas.Gend()
+
+ // draw pacman: two arcs, rotated,
+ // the angle determines how wide the mouth is open
+ canvas.Gstyle("fill:yellow")
+
+ canvas.TranslateRotate(cx, cy, -angle)
+ canvas.Arc(-r, 0, r, r, 30, false, true, r, 0)
+ canvas.Gend()
+
+ canvas.TranslateRotate(cx, cy, angle)
+ canvas.Arc(-r, 0, r, r, 30, false, false, r, 0)
+ canvas.Gend()
+
+ canvas.Gend()
+ canvas.End()
+}
+func tux(w http.ResponseWriter, req *http.Request) {
+ log.Printf("tux: %s", req.RemoteAddr)
+ w.Header().Set("Content-type", "image/svg+xml")
+ canvas := svg.New(w)
+ canvas.Start(width, height)
+ canvas.Rect(0, 0, width, height, "fill:white")
+ canvas.Circle(width/2, height/2, height/2-20, "fill:black")
+ canvas.Ellipse(width/2, height, width/2, height/2, "fill:black")
+ canvas.Ellipse(width/3, height/2, 20, 40, "fill:white")
+ canvas.Ellipse(2*width/3, height/2, 20, 40, "fill:white")
+ canvas.Ellipse(width/3, height/2+18, 15, 20)
+ canvas.Ellipse(2*width/3, height/2+18, 15, 20)
+
+ canvas.Circle(width/3+7, height/2+20, 5, "fill:white")
+ canvas.Circle(2*width/3+7, height/2+20, 5, "fill:white")
+
+ canvas.Arc(60, height-60, width/3, 50, 0, false, true, width-60, height-60,
+ "stroke-width:3;stroke-linecap:round;stroke:yellow;fill:yellow")
+
+ canvas.Arc(60, height-60, width/3, 140, 0, false, false, width-60, height-60,
+ "stroke-width:3;stroke-linecap:round;stroke:yellow")
+
+ beakx := []int{58, width - 58, width / 2}
+ beaky := []int{height - 62, height - 62, height - 20}
+ canvas.Polygon(beakx, beaky, "fill:yellow")
+
+ canvas.End()
+}
+
+const (
+ d2r = math.Pi / 180
+ ustyle = "stroke:#DD4814;stroke-width:8"
+)
+
+func polar(cx, cy, r, t int) (int, int) {
+ fr := float64(r)
+ ft := float64(t) * d2r
+ x := fr * math.Cos(ft)
+ y := fr * math.Sin(ft)
+ return cx + int(x), cy + int(y)
+}
+
+func ubuntu(w http.ResponseWriter, req *http.Request) {
+ log.Printf("ubuntu: %s", req.RemoteAddr)
+ w.Header().Set("Content-type", "image/svg+xml")
+ canvas := svg.New(w)
+ cx, cy := width/2, height/2
+ canvas.Start(width, height)
+ canvas.Rect(0, 0, width, height, "fill:white")
+ canvas.Circle(cx, cy, cx, "fill:#DD4814")
+ r := width / 3
+
+ canvas.Circle(cx, cy, r-10, "fill:none;stroke:white;stroke-width:25")
+ canvas.Gstyle("fill:white;" + ustyle)
+ for _, t := range []int{300, 180, 60} {
+ px, py := polar(cx, cy, r+10, t)
+ canvas.Circle(px, py, 20)
+ }
+ canvas.Gend()
+
+ canvas.Gstyle(ustyle)
+ for _, t := range []int{120, 0, 240} {
+ lx2, ly2 := polar(cx, cy, r+25, t)
+ canvas.Line(cx, cy, lx2, ly2)
+ }
+ canvas.Gend()
+
+ canvas.End()
+}
+
diff --git a/vendor/github.com/ajstarks/svgo/planets/planets.go b/vendor/github.com/ajstarks/svgo/planets/planets.go
new file mode 100644
index 0000000..5393d1b
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/planets/planets.go
@@ -0,0 +1,122 @@
+// planets: an exploration of scale
+// +build !appengine
+
+package main
+
+import (
+ "flag"
+ "image/png"
+ "os"
+
+ "github.com/ajstarks/svgo"
+)
+
+var ssDist = []float64{
+ 0.00, // Sun
+ 0.34, // Mercury
+ 0.72, // Venus
+ 1.00, // Earth
+ 1.54, // Mars
+ 5.02, // Jupiter
+ 9.46, // Saturn
+ 20.11, // Uranus
+ 30.08} // Netpune
+
+var ssRad = []float64{ // Miles
+ 423200.0, // Sun
+ 1516.0, // Mercury
+ 3760.0, // Venus
+ 3957.0, // Earth
+ 2104.0, // Mars
+ 42980.0, // Jupiter
+ 35610.0, // Saturn
+ 15700.0, // Uranus
+ 15260.0} // Neptune
+
+var ssColor = []string{ // R, G, B
+ // Eyeballed from image
+ "F7730C", // Sun
+ "FAF8F2", // Mercury
+ "FFFFF2", // Venus
+ "0B5CE3", // Earth
+ "F0C61D", // Mars
+ "FDC791", // Jupiter
+ "E0C422", // Saturn
+ "DCF1F5", // Uranus
+ "39B6F7"} // Neptune
+
+var ssImages = []string{
+ "sun.png",
+ "mercury.png",
+ "venus.png",
+ "earth.png",
+ "mars.png",
+ "jupiter.png",
+ "saturn.png",
+ "uranus.png",
+ "neptune.png"}
+
+var showdisk = flag.Bool("d", false, "show disk")
+var canvas = svg.New(os.Stdout)
+
+func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 {
+ return low2 + (high2-low2)*(value-low1)/(high1-low1)
+}
+
+func main() {
+
+ width := 1200
+ height := 200
+
+ flag.Parse()
+ canvas.Start(width, height)
+ canvas.Title("Planets")
+ canvas.Rect(0, 0, width, height, "fill:black")
+ nobj := len(ssDist)
+ y := height / 2
+ margin := 100
+ minsize := 7.0
+ labeloc := height / 4
+
+ var x, r, imScale, maxh float64
+ var px, po int
+
+ if *showdisk {
+ maxh = float64(height) / minsize
+ } else {
+ maxh = float64(height) / 4.0
+ }
+ for i := 1; i < nobj; i++ {
+ x = vmap(ssDist[i], ssDist[1], ssDist[nobj-1], float64(margin), float64(width-margin))
+ r = (vmap(ssRad[i], ssRad[1], ssRad[nobj-1], minsize, maxh)) / 2
+ px = int(x)
+ if *showdisk {
+ po = 0
+ canvas.Circle(px, y, int(r), "fill:#"+ssColor[i])
+ } else { // show images
+ f, err := os.Open(ssImages[i])
+ if err != nil {
+ println("bad image file:", ssImages[i])
+ continue
+ }
+ defer f.Close()
+ p, perr := png.DecodeConfig(f)
+ if perr != nil {
+ println("bad decode:", ssImages[i])
+ continue
+ }
+ imScale = r / float64(p.Width)
+ hs := float64(p.Height) * imScale
+ dy := y - (int(hs) / 2) // center the image
+ po = int(r) / 2
+ canvas.Image(px, dy, int(r), int(hs), ssImages[i])
+ }
+ if ssDist[i] == 1.0 { // earth
+ canvas.Line(px+po, y-po, px+po, y-labeloc,
+ "stroke-width:1px;stroke:white")
+ canvas.Text(px+po, y-labeloc-10, "You are here",
+ "fill:white; font-size:14px; font-family:Calibri; text-anchor:middle")
+ }
+ }
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/pmap/pmap.go b/vendor/github.com/ajstarks/svgo/pmap/pmap.go
new file mode 100644
index 0000000..489fe25
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/pmap/pmap.go
@@ -0,0 +1,244 @@
+// pmap percentage maps
+// +build !appengine
+
+package main
+
+import (
+ "encoding/xml"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+
+ "github.com/ajstarks/svgo"
+)
+
+// Pmap defines a porportional map
+type Pmap struct {
+ Top int `xml:"top,attr"`
+ Left int `xml:"left,attr"`
+ Title string `xml:"title,attr"`
+ Pdata []Pdata `xml:"pdata"`
+}
+
+// Pdata defines data with a portpotional map
+type Pdata struct {
+ Legend string `xml:"legend,attr"`
+ Stagger string `xml:"stagger,attr"`
+ Alternate string `xml:"alternate,attr"`
+ Item []Item `xml:"item"`
+}
+
+// Item defines an item with porpotional map data
+type Item struct {
+ Name string `xml:",chardata"`
+ Value float64 `xml:"value,attr"`
+}
+
+var (
+ width, height, fontsize, fontscale, round, gutter, pred, pgreen, pblue, oflen int
+ bgcolor, olcolor, colorspec, title string
+ showpercent, showdata, alternate, showtitle, stagger, showlegend, showtotal bool
+ ofpct float64
+ leftmargin = 40
+ topmargin = 40
+ canvas = svg.New(os.Stdout)
+)
+
+const (
+ globalfmt = "stroke-width:1;font-family:Calibri,sans-serif;text-anchor:middle;font-size:%dpt"
+ legendstyle = "text-anchor:start;font-size:150%"
+ linefmt = "stroke:%s"
+)
+
+func dopmap(location string) {
+ var f *os.File
+ var err error
+ if len(location) > 0 {
+ f, err = os.Open(location)
+ } else {
+ f = os.Stdin
+ }
+ if err == nil {
+ readpmap(f)
+ f.Close()
+ } else {
+ fmt.Fprintf(os.Stderr, "%v\n", err)
+ }
+}
+
+func readpmap(r io.Reader) {
+ var pm Pmap
+ if err := xml.NewDecoder(r).Decode(&pm); err == nil {
+ drawpmap(pm)
+ } else {
+ fmt.Fprintf(os.Stderr, "Unable to parse pmap (%v)\n", err)
+ }
+}
+
+func drawpmap(m Pmap) {
+ fs := fontsize
+ if m.Left > 0 {
+ leftmargin = m.Left
+ }
+ if m.Top > 0 {
+ topmargin = m.Top
+ } else {
+ topmargin = fs * fontscale
+ }
+ x := leftmargin
+ y := topmargin
+ if len(m.Title) > 0 {
+ title = m.Title
+ }
+ canvas.Title(title)
+ if showtitle {
+ dotitle(title)
+ }
+ for _, p := range m.Pdata {
+ pmap(x, y, fs, p)
+ y += fs*fontscale + (gutter + fs*2)
+ }
+}
+
+func pmap(x, y, fs int, m Pdata) {
+ var tfill, vfmt, oc string
+ var up bool
+ h := fs * fontscale
+ fw := fs * 80
+ slen := fs + (fs / 2)
+ up = false
+
+ sum := 0.0
+ for _, v := range m.Item {
+ sum += v.Value
+ }
+
+ if len(olcolor) > 0 {
+ oc = olcolor
+ } else {
+ oc = bgcolor
+ }
+ loffset := (fs * fontscale) + fs
+ gline := fmt.Sprintf(linefmt, "gray")
+ wline := fmt.Sprintf(linefmt, oc)
+ if len(m.Legend) > 0 && showlegend {
+ if showtotal {
+ canvas.Text(x, y-fs, fmt.Sprintf("%s (total: "+floatfmt(sum)+")", m.Legend, sum), legendstyle)
+ } else {
+ canvas.Text(x, y-fs, m.Legend, legendstyle)
+ }
+ }
+ for i, p := range m.Item {
+ k := p.Name
+ v := p.Value
+ if v == 0.0 {
+ continue
+ }
+ pct := v / sum
+ pw := int(pct * float64(fw))
+ xw := x + (pw / 2)
+ yh := y + (h / 2)
+ if pct >= .4 {
+ tfill = "fill:white"
+ } else {
+ tfill = "fill:black"
+ }
+ if round > 0 {
+ canvas.Roundrect(x, y, pw, h, round, round, canvas.RGBA(pred, pgreen, pblue, pct))
+ } else {
+ canvas.Rect(x, y, pw, h, canvas.RGBA(pred, pgreen, pblue, pct))
+ }
+
+ dy := yh + fs + (fs / 2)
+ if pct <= ofpct || len(k) > oflen { // overflow label
+ if up {
+ dy -= loffset
+ yh -= loffset
+ canvas.Line(xw, y, xw, dy+(fs/2), gline)
+ } else {
+ dy += loffset
+ yh += loffset
+ canvas.Line(xw, y+h, xw, dy-(fs*3), gline)
+ }
+ if alternate {
+ up = !up
+ slen = fs * 2
+ } else {
+ slen = fs * 3
+ }
+ if stagger {
+ loffset += slen
+ }
+ tfill = "fill:black"
+ }
+ canvas.Text(xw, yh, k, tfill)
+ dpfmt := tfill + ";font-size:75%"
+ vfmt = floatfmt(v)
+ switch {
+ case showpercent && !showdata:
+ canvas.Text(xw, dy, fmt.Sprintf("%.1f%%", pct*100), dpfmt)
+ case showpercent && showdata:
+ canvas.Text(xw, dy, fmt.Sprintf(vfmt+", %.1f%%", v, pct*100), dpfmt)
+ case showdata && !showpercent:
+ canvas.Text(xw, dy, fmt.Sprintf(vfmt, v), dpfmt)
+ }
+ x += pw
+ if i < len(m.Item) {
+ canvas.Line(x, y, x, y+h, wline)
+ }
+ }
+}
+
+func floatfmt(v float64) string {
+ var vfmt = "%.1f"
+ if v-float64(int(v)) == 0.0 {
+ vfmt = "%.0f"
+ }
+ return vfmt
+}
+
+func dotitle(s string) {
+ offset := 40
+ canvas.Text(leftmargin, height-offset, s, "text-anchor:start;font-size:250%")
+}
+
+func init() {
+ flag.IntVar(&width, "w", 1024, "width")
+ flag.IntVar(&height, "h", 768, "height")
+ flag.IntVar(&fontsize, "f", 12, "font size (pt)")
+ flag.IntVar(&fontscale, "s", 5, "font scaling factor")
+ flag.IntVar(&round, "r", 0, "rounded corner size")
+ flag.IntVar(&gutter, "g", 100, "gutter")
+ flag.IntVar(&oflen, "ol", 20, "overflow length")
+ flag.StringVar(&bgcolor, "bg", "white", "background color")
+ flag.StringVar(&olcolor, "oc", "", "outline color")
+ flag.StringVar(&colorspec, "c", "0,0,0", "color (r,g,b)")
+ flag.StringVar(&title, "t", "Proportions", "title")
+ flag.BoolVar(&showpercent, "p", false, "show percentage")
+ flag.BoolVar(&showdata, "d", false, "show data")
+ flag.BoolVar(&alternate, "a", false, "alternate overflow labels")
+ flag.BoolVar(&stagger, "stagger", false, "stagger labels")
+ flag.BoolVar(&showlegend, "showlegend", true, "show the legend")
+ flag.BoolVar(&showtitle, "showtitle", false, "show the title")
+ flag.BoolVar(&showtotal, "showtotal", false, "show totals in the legend")
+ flag.Float64Var(&ofpct, "op", 0.05, "overflow percentage")
+ flag.Parse()
+ fmt.Sscanf(colorspec, "%d,%d,%d", &pred, &pgreen, &pblue)
+}
+
+func main() {
+ canvas.Start(width, height)
+ canvas.Rect(0, 0, width, height, "fill:"+bgcolor)
+ canvas.Gstyle(fmt.Sprintf(globalfmt, fontsize))
+
+ if len(flag.Args()) == 0 {
+ dopmap("")
+ } else {
+ for _, f := range flag.Args() {
+ dopmap(f)
+ }
+ }
+ canvas.Gend()
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/randcomp/randcomp.go b/vendor/github.com/ajstarks/svgo/randcomp/randcomp.go
new file mode 100644
index 0000000..696f67a
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/randcomp/randcomp.go
@@ -0,0 +1,59 @@
+// randcomp visualizes random number generators
+// +build !appengine
+
+package main
+
+import (
+ "fmt"
+ "math/rand"
+ "os"
+ "strconv"
+ "time"
+
+ "github.com/ajstarks/svgo"
+)
+
+var canvas = svg.New(os.Stdout)
+
+func main() {
+ width := 512
+ height := 256
+ var n = 256
+ var rx, ry int
+
+ if len(os.Args) > 1 {
+ n, _ = strconv.Atoi(os.Args[1])
+ }
+
+ f, _ := os.Open("/dev/urandom")
+ x := make([]byte, n)
+ y := make([]byte, n)
+ f.Read(x)
+ f.Read(y)
+ f.Close()
+
+ rand.Seed(int64(time.Now().Nanosecond()) % 1e9)
+ canvas.Start(600, 400)
+ canvas.Title("Random Integer Comparison")
+ canvas.Desc("Comparison of Random integers: the random device & the Go rand package")
+ canvas.Rect(0, 0, width/2, height, "fill:white; stroke:gray")
+ canvas.Rect(width/2, 0, width/2, height, "fill:white; stroke:gray")
+
+ canvas.Desc("Left: Go rand package (red), Right: /dev/urandom")
+ canvas.Gstyle("stroke:none; fill-opacity:0.5")
+ for i := 0; i < n; i++ {
+ rx = rand.Intn(255)
+ ry = rand.Intn(255)
+ canvas.Circle(rx, ry, 5, canvas.RGB(127, 0, 0))
+ canvas.Circle(int(x[i])+255, int(y[i]), 5, "fill:black")
+ }
+ canvas.Gend()
+
+ canvas.Desc("Legends")
+ canvas.Gstyle("text-anchor:middle; font-size:18; font-family:Calibri")
+ canvas.Text(128, 280, "Go rand package", "")
+ canvas.Text(384, 280, "/dev/urandom")
+ canvas.Text(256, 280, fmt.Sprintf("n=%d", n), "font-size:12")
+ canvas.Gend()
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/richter/richter.go b/vendor/github.com/ajstarks/svgo/richter/richter.go
new file mode 100644
index 0000000..a99bdee
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/richter/richter.go
@@ -0,0 +1,35 @@
+// richter -- inspired by Gerhard Richter's 256 colors, 1974
+// +build !appengine
+
+package main
+
+import (
+ "math/rand"
+ "os"
+ "time"
+
+ "github.com/ajstarks/svgo"
+)
+
+var canvas = svg.New(os.Stdout)
+
+var width = 700
+var height = 400
+
+func main() {
+ rand.Seed(int64(time.Now().Nanosecond()) % 1e9)
+ canvas.Start(width, height)
+ canvas.Title("Richter")
+ canvas.Rect(0, 0, width, height, "fill:white")
+ rw := 32
+ rh := 18
+ margin := 5
+ for i, x := 0, 20; i < 16; i++ {
+ x += (rw + margin)
+ for j, y := 0, 20; j < 16; j++ {
+ canvas.Rect(x, y, rw, rh, canvas.RGB(rand.Intn(255), rand.Intn(255), rand.Intn(255)))
+ y += (rh + margin)
+ }
+ }
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/rl/rl.go b/vendor/github.com/ajstarks/svgo/rl/rl.go
new file mode 100644
index 0000000..a233d4c
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/rl/rl.go
@@ -0,0 +1,34 @@
+// rl - draw random lines
+// +build !appengine
+
+package main
+
+import (
+ "fmt"
+ "math/rand"
+ "os"
+ "time"
+
+ "github.com/ajstarks/svgo"
+)
+
+var canvas = svg.New(os.Stdout)
+
+func main() {
+ width := 200
+ height := 200
+ canvas.Start(width, height)
+ canvas.Title("Random Lines")
+ canvas.Rect(0, 0, width, height, "fill:black")
+ rand.Seed(int64(time.Now().Nanosecond()) % 1e9)
+ canvas.Gstyle("stroke-width:10")
+ r := 0
+ for i := 0; i < width; i++ {
+ r = rand.Intn(255)
+ canvas.Line(i, 0, rand.Intn(width), height, fmt.Sprintf("stroke:rgb(%d,%d,%d); opacity:0.39", r, r, r))
+ }
+ canvas.Gend()
+
+ canvas.Text(width/2, height/2, "Random Lines", "fill:white; font-size:20; font-family:Calibri; text-anchor:middle")
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/rpd/rpd.go b/vendor/github.com/ajstarks/svgo/rpd/rpd.go
new file mode 100644
index 0000000..397c418
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/rpd/rpd.go
@@ -0,0 +1,80 @@
+package main
+
+// Imports
+import (
+ "encoding/xml"
+ "flag"
+ "fmt"
+ "github.com/ajstarks/svgo"
+ "io"
+ "os"
+)
+
+//
+// - This is small
+// - This is medium
+// - This is large
+//
+
+type Thing struct {
+ Top int `xml:"top,attr"`
+ Left int `xml:"left,attr"`
+ Sep int `xml:"sep,attr"`
+ Item []item `xml:"item"`
+}
+
+type item struct {
+ Width int `xml:"width,attr"`
+ Height int `xml:"height,attr"`
+ Name string `xml:"name,attr"`
+ Color string `xml:"color,attr"`
+ Text string `xml:",chardata"`
+}
+
+var (
+ width = flag.Int("w", 1024, "width")
+ height = flag.Int("h", 768, "height")
+ canvas = svg.New(os.Stdout)
+)
+
+// Open the file
+func dothing(location string) {
+ f, err := os.Open(location)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%v\n", err)
+ return
+ }
+ defer f.Close()
+ readthing(f)
+}
+
+// Read the file, loading the defined structure
+func readthing(r io.Reader) {
+ var t Thing
+ if err := xml.NewDecoder(r).Decode(&t); err != nil {
+ fmt.Fprintf(os.Stderr, "Unable to parse components (%v)\n", err)
+ return
+ }
+ drawthing(t)
+}
+
+// use the items of "thing" to make the picture
+func drawthing(t Thing) {
+ x := t.Left
+ y := t.Top
+ for _, v := range t.Item {
+ style := fmt.Sprintf("font-size:%dpx;fill:%s", v.Width/2, v.Color)
+ canvas.Circle(x, y, v.Height/4, "fill:"+v.Color)
+ canvas.Text(x+t.Sep, y, v.Name+":"+v.Text+"/"+v.Color, style)
+ y += v.Height
+ }
+}
+
+func main() {
+ flag.Parse()
+ for _, f := range flag.Args() {
+ canvas.Start(*width, *height)
+ dothing(f)
+ canvas.End()
+ }
+}
diff --git a/vendor/github.com/ajstarks/svgo/rpd/thing.xml b/vendor/github.com/ajstarks/svgo/rpd/thing.xml
new file mode 100644
index 0000000..fbd9d0c
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/rpd/thing.xml
@@ -0,0 +1,5 @@
+
+ - This is small
+ - This is medium
+ - This is large
+
diff --git a/vendor/github.com/ajstarks/svgo/rr/rr.go b/vendor/github.com/ajstarks/svgo/rr/rr.go
new file mode 100644
index 0000000..0f93c83
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/rr/rr.go
@@ -0,0 +1,208 @@
+// radar roadmap (via Ernst and Young)
+package main
+
+import (
+ "encoding/xml"
+ "flag"
+ "fmt"
+ "github.com/ajstarks/svgo"
+ "io"
+ "math"
+ "os"
+ "strings"
+)
+
+var (
+ width, height, iscale, fontsize, margin int
+ bgcolor, itemcolor, title string
+ opacity float64
+ showtitle bool
+ gstyle = "font-family:Calibri;font-size:%dpx;text-anchor:middle"
+ canvas = svg.New(os.Stdout)
+)
+
+// Roadmap XML structure:
+// a roadmap consists of sections, which contain items, which indicate maturity and impact
+type Roadmap struct {
+ Title string `xml:"title,attr"`
+ Duration int `xml:"duration,attr"`
+ Unit string `xml:"unit,attr"`
+ Section []section `xml:"section"`
+}
+
+type section struct {
+ Name string `xml:"name,attr"`
+ Spacing float64 `xml:"spacing,attr"`
+ Item []item `xml:"item"`
+}
+
+type item struct {
+ Impact int `xml:"impact,attr"`
+ Effort int `xml:"effort,attr"`
+ Begin string `xml:"begin,attr"`
+ Age float64 `xml:"age,attr"`
+ Name string `xml:",chardata"`
+ Desc desc `xml:"desc"`
+}
+
+type desc struct {
+ Description string `xml:",chardata"`
+}
+
+// dorr does file i/o
+func dorr(location string) {
+ var f *os.File
+ var err error
+ if len(location) > 0 {
+ f, err = os.Open(location)
+ } else {
+ f = os.Stdin
+ }
+ if err == nil {
+ readrr(f)
+ f.Close()
+ } else {
+ fmt.Fprintf(os.Stderr, "%v\n", err)
+ }
+}
+
+// readrr reads and parses the XML specification
+func readrr(r io.Reader) {
+ var rm Roadmap
+ if err := xml.NewDecoder(r).Decode(&rm); err == nil {
+ drawrr(rm)
+ } else {
+ fmt.Fprintf(os.Stderr, "%v\n", err)
+ }
+}
+
+// drawrr draws the roadmap
+func drawrr(rm Roadmap) {
+
+ if len(rm.Title) > 0 {
+ title = rm.Title
+ }
+ canvas.Title(title)
+ canvas.Gstyle(fmt.Sprintf(gstyle, fontsize))
+ canvas.Rect(0, 0, width, height, "fill:"+bgcolor)
+ duration := rm.Duration
+ if duration <= 0 {
+ duration = 3
+ }
+ ns := len(rm.Section)
+ cx := (width / 2)
+ cy := (height / 2)
+ r := ((width - margin) / duration) / 2
+ sr := r
+ midsize := width / 100
+
+ // for each unit of time, draw cencentric circles
+ for i := 0; i < duration; i++ {
+ canvas.Circle(cx, cy, sr, "fill:none;stroke:lightgray;stroke-dasharray:7,7")
+ canvas.Text(cx, (cy - sr), fmt.Sprintf("%s %d", rm.Unit, i+1), "font-size:150%;fill:gray")
+ sr += r
+ }
+ // for each section, define its boundaries and draw its label
+ angle := 360.0 / float64(ns)
+ a := angle
+ a2 := a / 2
+ for _, s := range rm.Section {
+ drawseclines(cx, cy, float64(r*duration), a, a2, s.Name)
+ spacing := s.Spacing
+ if spacing == 0 {
+ spacing = (angle / float64(len(s.Item))) - 1
+ }
+ iangle := a + spacing
+ // for each item in the section, place the marker and label
+ for _, i := range s.Item {
+ itemx, itemy := dpolar(cx, cy, i.Age*float64(r), iangle)
+ drawitem(itemx, itemy, i.Impact*iscale, i.Effort, i.Name)
+ iangle += spacing
+ }
+ a += angle
+ }
+
+ canvas.Circle(cx, cy, midsize, "fill:red")
+ canvas.Text(cx, cy, "READY", "baseline-shift:-25%")
+ if showtitle {
+ dotitle(title)
+ }
+ canvas.Gend()
+}
+
+// radians converts degrees to radians
+func radians(d float64) float64 {
+ return d * (math.Pi / 180.0)
+}
+
+// dotitle places the title text
+func dotitle(s string) {
+ canvas.Text(width/2, height-10, s, "font-size:200%;text-anchor:middle")
+}
+
+// dpolar returns the cartesion coordinates given the center, size, and angle (in degrees)
+func dpolar(cx, cy int, r, d float64) (int, int) {
+ x := r * math.Cos(radians(d))
+ y := r * math.Sin(radians(d))
+ return cx + int(x), cy + int(y)
+}
+
+// drawseclines defines and labels the sections
+func drawseclines(cx, cy int, size, a, h float64, s string) {
+ fs := fontsize + (fontsize / 2)
+ ix, iy := dpolar(cx, cy, size, a)
+ ix2, iy2 := dpolar(cx, cy, size+50, a+h)
+ canvas.Line(cx, cy, ix, iy, "stroke:lightgray")
+ textlines(ix2, iy2, fs, fs+2, "middle", "black", strings.Split(s, "\\n"))
+}
+
+// drawitem draws a roadmap item
+func drawitem(x, y, isize, ieffort int, s string) {
+ var op float64
+ if ieffort > 0 {
+ op = opacity * (float64(ieffort) / 10.0)
+ } else {
+ op = opacity
+ }
+ style := fmt.Sprintf("fill:%s;fill-opacity:%.2f;stroke:white", itemcolor, op)
+ canvas.Circle(x, y, isize/2, style)
+ textlines(x-(isize/2)-2, y, fontsize, fontsize+2, "end", "black", strings.Split(s, "\\n"))
+}
+
+// textlines displays text at a specified size, leading, fill, and alignment
+func textlines(x, y, fs, leading int, align, fill string, s []string) {
+ canvas.Gstyle(fmt.Sprintf("font-size:%dpx;text-anchor:%s;fill:%s", fs, align, fill))
+ for _, v := range s {
+ canvas.Text(x, y, v)
+ y += leading
+ }
+ canvas.Gend()
+}
+
+// init sets up the command flags
+func init() {
+ flag.StringVar(&bgcolor, "bg", "white", "background color")
+ flag.StringVar(&itemcolor, "ic", "rgb(131,206,226)", "item color")
+ flag.IntVar(&width, "w", 800, "width")
+ flag.IntVar(&height, "h", 800, "height")
+ flag.IntVar(&fontsize, "f", 12, "fontsize (px)")
+ flag.IntVar(&iscale, "s", int(float64(width)*.009), "impact scale")
+ flag.IntVar(&margin, "m", 150, "outside margin")
+ flag.BoolVar(&showtitle, "showtitle", false, "show title")
+ flag.StringVar(&title, "t", "Roadmap", "title")
+ flag.Float64Var(&opacity, "o", 1.0, "opacity")
+ flag.Parse()
+}
+
+// for every input file (or stdin), draw a roadmap as specified by command flags
+func main() {
+ canvas.Start(width, height)
+ if len(flag.Args()) == 0 {
+ dorr("")
+ } else {
+ for _, f := range flag.Args() {
+ dorr(f)
+ }
+ }
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/shotchart/shotchart.go b/vendor/github.com/ajstarks/svgo/shotchart/shotchart.go
new file mode 100644
index 0000000..107834c
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/shotchart/shotchart.go
@@ -0,0 +1,150 @@
+// shotchart: make NBA shotcharts
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+
+ "github.com/ajstarks/svgo"
+)
+
+// shotdata defines the shotchart JSON response from stats.nba.com
+type shotdata struct {
+ Resource string `json:"resource"`
+ Parameters struct {
+ Leagueid string `json:"LeagueID"`
+ Season string `json:"Season"`
+ Seasontype string `json:"SeasonType"`
+ Teamid int `json:"TeamID"`
+ Playerid int `json:"PlayerID"`
+ Contextfilter string `json:"ContextFilter"`
+ Contextmeasure string `json:"ContextMeasure"`
+ } `json:"parameters"`
+ Resultsets []struct {
+ Name string `json:"name"`
+ Headers []string `json:"headers"`
+ Rowset [][]interface{} `json:"rowSet"`
+ } `json:"resultSets"`
+}
+
+// vmap maps one range into another
+func vmap(value, low1, high1, low2, high2 float64) float64 {
+ return low2 + (high2-low2)*(value-low1)/(high1-low1)
+}
+
+const (
+ shotsURLfmt = "http://stats.nba.com/stats/shotchartdetail?PlayerID=%s&CFID=33&CFPARAMS=2014-15&ContextFilter=&ContextMeasure=FGA&DateFrom=&DateTo=&GameID=&GameSegment=&LastNGames=0&LeagueID=00&Location=&MeasureType=Base&Month=0&OpponentTeamID=0&Outcome=&PaceAdjust=N&PerMode=PerGame&Period=0&PlusMinus=N&Position=&Rank=N&RookieYear=&Season=2014-15&SeasonSegment=&SeasonType=Regular+Season&TeamID=0&VsConference=&VsDivision=&mode=Advanced&showDetails=0&showShots=1&showZones=0"
+ picURLfmt = "http://stats.nba.com/media/players/230x185/%s.png"
+ activepicURLfmt = "http://stats.nba.com/media/players/700/%s.png"
+)
+
+// shotAPI retrieves shotchart data from the source (currently stats.nba.com)
+func shotAPI(id string) (io.ReadCloser, error) {
+ resp, err := http.Get(fmt.Sprintf(shotsURLfmt, id))
+ if err != nil {
+ return nil, err
+ }
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("unable to retreive network data for %s (%s)", id, resp.Status)
+ }
+ return resp.Body, nil
+}
+
+// shotchart retrieves shot data given an id, either from local files or the network API
+func shotchart(id string, network bool) {
+ var (
+ shots shotdata
+ r io.ReadCloser
+ err error
+ picture string
+ )
+
+ if network {
+ r, err = shotAPI(id)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%v\n", err)
+ return
+ }
+ picture = fmt.Sprintf(activepicURLfmt, id)
+ } else {
+ r, err = os.Open(id + ".json")
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%v\n", err)
+ return
+ }
+ picture = id + ".png"
+ }
+
+ defer r.Close()
+ err = json.NewDecoder(r).Decode(&shots)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%v\n", err)
+ return
+ }
+
+ // define the canvas, read and parse the response
+ canvas := svg.New(os.Stdout)
+ width, height := 900, 846
+ fw, fh := float64(width), float64(height)
+ //imw, imh := 230, 185
+ imw, imh := 700, 440
+ top := fw / 10
+ canvas.Start(width, height)
+ canvas.Rect(0, 0, width, height, "fill:white;stroke:black;stroke-width:2")
+ //canvas.Image(width-imw, height-imh, imw, imh, picture)
+ canvas.Image(width/2, height-imh, imw, imh, picture)
+ canvas.Gstyle("font-family:Calibri;sans-serif;font-size:24px")
+ nfg := 0
+ for _, r := range shots.Resultsets {
+ if r.Name == "Shot_Chart_Detail" {
+ var playername string
+ attempts := len(r.Rowset)
+ for _, rs := range r.Rowset {
+ var x, y float64
+ var fill string
+ for i, v := range rs {
+ if i == 4 {
+ playername = v.(string)
+ }
+ if i == 17 {
+ x = v.(float64)
+ }
+ if i == 18 {
+ y = v.(float64)
+ }
+ if i == 20 {
+ xp := int(vmap(x, -300, 300, 0, fw))
+ yp := int(vmap(y, -300, 300, top, fh))
+ if v.(float64) == 0 {
+ fill = "red"
+ } else {
+ nfg++
+ fill = "black"
+ }
+ canvas.Circle(xp, (yp-(height/2))+10, 4, "fill-opacity:0.3;fill:"+fill)
+ }
+ }
+ }
+ fgpct := (float64(nfg) / float64(attempts)) * 100
+ canvas.Text(10, height-40, playername, "fill:gray")
+ canvas.Text(10, height-10, fmt.Sprintf("%d out of %d", attempts, nfg), "fill:gray")
+ canvas.Text(width/2, height-10, fmt.Sprintf("%.1f%%", fgpct), "text-anchor:middle;font-size:120%;fill:gray")
+ canvas.Gend()
+ }
+ }
+ canvas.End()
+
+}
+func main() {
+ var network bool
+ flag.BoolVar(&network, "net", false, "retrieve data from the network")
+ flag.Parse()
+
+ for _, file := range flag.Args() {
+ shotchart(file, network)
+ }
+}
diff --git a/vendor/github.com/ajstarks/svgo/skewabc/skewabc.go b/vendor/github.com/ajstarks/svgo/skewabc/skewabc.go
new file mode 100644
index 0000000..01e15d3
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/skewabc/skewabc.go
@@ -0,0 +1,37 @@
+// skewabc - exercise the skew functions
+// +build !appengine
+
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/ajstarks/svgo"
+)
+
+var (
+ g = svg.New(os.Stdout)
+ width = 500
+ height = 500
+)
+
+func sky(x, y, w, h int, a float64, s string) {
+ g.Gstyle(fmt.Sprintf("font-family:sans-serif;font-size:%dpx;text-anchor:middle", w/2))
+ g.SkewY(a)
+ g.Rect(x, y, w, h, `fill:black; fill-opacity:0.3`)
+ g.Text(x+w/2, y+h/2, s, `fill:white;baseline-shift:-33%`)
+ g.Gend()
+ g.Gend()
+}
+
+func main() {
+ g.Start(width, height)
+ g.Title("Skew")
+ g.Rect(0, 0, width, height, "fill:white")
+ g.Grid(0, 0, width, height, 50, "stroke:lightblue")
+ sky(100, 100, 100, 100, 30, "A")
+ sky(200, 332, 100, 100, -30, "B")
+ sky(300, -15, 100, 100, 30, "C")
+ g.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/stockproduct/stockproduct.go b/vendor/github.com/ajstarks/svgo/stockproduct/stockproduct.go
new file mode 100644
index 0000000..a59a15e
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/stockproduct/stockproduct.go
@@ -0,0 +1,179 @@
+// stockproduct draws a bar chart comparing stock price to products
+// +build !appengine
+
+package main
+
+import (
+ "encoding/xml"
+ "flag"
+ "fmt"
+ "os"
+
+ "github.com/ajstarks/svgo"
+)
+
+// Parameters defines options
+type Parameters struct {
+ showline, showimage, showproduct, showprice, showdate, showgrid bool
+ x, y, w, h, width, height, spacing, fontsize, dot int
+ minvalue, maxvalue, ginterval, opacity, rotatetext float64
+ barcolor string
+}
+
+//
+//
+//
+//
+//
+//
+//
+//
+
+// StockProduct is the top-level drawing
+type StockProduct struct {
+ Title string `xml:"title,attr"`
+ Sdata []Sdata `xml:"sdata"`
+}
+
+// Sdata defines stock data
+type Sdata struct {
+ Price float64 `xml:"price,attr"`
+ Date string `xml:"date,attr"`
+ Product string `xml:"product,attr"`
+ Image string `xml:"image,attr"`
+}
+
+// vmap maps ranges
+func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 {
+ return low2 + (high2-low2)*(value-low1)/(high1-low1)
+
+}
+
+// barchart draws a chart from data read at location, on a SVG canvas
+// if the location is the empty string, read from standard input.
+// Data items are scaled according to the width, with parameters controlling the visibility
+// of lines, products, images, and dates
+func (p *Parameters) barchart(location string, canvas *svg.SVG) {
+ var (
+ f *os.File
+ err error
+ sp StockProduct
+ )
+ if len(location) > 0 {
+ f, err = os.Open(location)
+ } else {
+ f = os.Stdin
+ }
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ return
+ }
+ defer f.Close()
+ if err := xml.NewDecoder(f).Decode(&sp); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ return
+ }
+
+ bottom := p.y + p.h
+ interval := p.w / (len(sp.Sdata) - 1)
+ bw := interval - p.spacing
+ offset := 120
+ halfoffset := offset / 2
+
+ if bw < 2 {
+ bw = 2
+ }
+ canvas.Text(p.x, p.y-halfoffset, sp.Title, "font-size:400%")
+ if p.showgrid {
+ canvas.Gstyle("stroke:lightgray;stroke-width:1px")
+ gx := p.x - (bw / 2)
+ for i := p.maxvalue; i >= p.minvalue; i -= p.ginterval {
+ yp := int(vmap(i, p.minvalue, p.maxvalue, float64(p.y), float64(bottom)))
+ by := p.y + (bottom - yp)
+ canvas.Line(gx, by, p.x+p.w+(bw/2), by)
+ canvas.Text(gx-halfoffset, by, fmt.Sprintf("%.0f", i), "fill:black;stroke:none")
+ }
+ canvas.Gend()
+ }
+ canvas.Gstyle(fmt.Sprintf("stroke-opacity:%.2f;stroke:%s;stroke-width:%d;text-anchor:middle", p.opacity, p.barcolor, bw))
+ for _, d := range sp.Sdata {
+ yp := int(vmap(d.Price, p.minvalue, p.maxvalue, float64(p.y), float64(bottom)))
+ by := p.y + (bottom - yp)
+ if p.showline {
+ canvas.Line(p.x, bottom, p.x, by)
+ }
+ if p.dot > 0 {
+ canvas.Circle(p.x, by, p.dot, fmt.Sprintf("stroke:none;fill-opacity:%.2f;fill:%s", p.opacity, p.barcolor))
+ }
+ if p.showimage {
+ if len(d.Image) > 0 {
+ canvas.Image(p.x-bw/2, by-offset-2, bw, offset, d.Image)
+ }
+ }
+ canvas.Gstyle("stroke:none;fill:black")
+ if p.showproduct {
+ if p.rotatetext != 0 {
+ canvas.TranslateRotate(p.x, bottom+40, p.rotatetext)
+ canvas.Text(0, 0, d.Product)
+ canvas.Gend()
+ } else {
+ canvas.Text(p.x, bottom+40, d.Product)
+ }
+ }
+ if p.showprice {
+ canvas.Text(p.x, by, fmt.Sprintf("%.2f", d.Price), "font-weight:bold")
+ }
+ if p.showdate {
+ canvas.Text(p.x, bottom+20, d.Date)
+ }
+ canvas.Gend()
+ p.x += interval
+ }
+ canvas.Gend()
+}
+
+var param Parameters
+
+// set parameters according to command flags
+func init() {
+ flag.BoolVar(¶m.showline, "line", true, "show lines")
+ flag.BoolVar(¶m.showimage, "image", true, "show images")
+ flag.BoolVar(¶m.showproduct, "product", true, "show products")
+ flag.BoolVar(¶m.showprice, "price", true, "show prices")
+ flag.BoolVar(¶m.showdate, "date", true, "show dates")
+ flag.BoolVar(¶m.showgrid, "grid", true, "show grid")
+ flag.IntVar(¶m.width, "w", 1600, "overall width")
+ flag.IntVar(¶m.height, "h", 900, "overall height")
+ flag.IntVar(¶m.x, "left", 150, "left")
+ flag.IntVar(¶m.y, "top", 120, "top")
+ flag.IntVar(¶m.w, "gw", 1400, "graph width")
+ flag.IntVar(¶m.h, "gh", 700, "graph height")
+ flag.IntVar(¶m.dot, "dot", 0, "dotsize")
+ flag.IntVar(¶m.fontsize, "fs", 14, "font size (px)")
+ flag.IntVar(¶m.spacing, "spacing", 15, "bar spacing")
+ flag.Float64Var(¶m.maxvalue, "max", 400, "max value")
+ flag.Float64Var(¶m.minvalue, "min", 0, "max value")
+ flag.Float64Var(¶m.ginterval, "ginterval", 50, "max value")
+ flag.Float64Var(¶m.opacity, "opacity", 0.5, "bar opacity")
+ flag.Float64Var(¶m.rotatetext, "rt", 0, "rotate text")
+ flag.StringVar(¶m.barcolor, "color", "lightgray", "bar color")
+ flag.Parse()
+}
+
+func main() {
+ width := 1600
+ height := 900
+ canvas := svg.New(os.Stdout)
+ canvas.Start(param.width, param.height)
+ canvas.Rect(0, 0, width, height, canvas.RGB(255, 255, 255))
+ canvas.Gstyle(fmt.Sprintf("font-family:Calibri;font-size:%dpx", param.fontsize))
+ if len(flag.Args()) == 0 {
+ param.barchart("", canvas)
+ } else {
+ for _, f := range flag.Args() {
+ param.barchart(f, canvas)
+ }
+ }
+ canvas.Gend()
+ canvas.End()
+}
diff --git a/vendor/github.com/ajstarks/svgo/svg.go b/vendor/github.com/ajstarks/svgo/svg.go
new file mode 100644
index 0000000..b3b7661
--- /dev/null
+++ b/vendor/github.com/ajstarks/svgo/svg.go
@@ -0,0 +1,978 @@
+// Package svg provides an API for generating Scalable Vector Graphics (SVG)
+package svg
+
+// package main
+//
+// import (
+// "github.com/ajstarks/svgo"
+// "os"
+// )
+//
+// var (
+// width = 500
+// height = 500
+// canvas = svg.New(os.Stdout)
+// )
+//
+// func main() {
+// canvas.Start(width, height)
+// canvas.Circle(width/2, height/2, 100)
+// canvas.Text(width/2, height/2, "Hello, SVG",
+// "text-anchor:middle;font-size:30px;fill:white")
+// canvas.End()
+// }
+//
+
+import (
+ "fmt"
+ "io"
+
+ "encoding/xml"
+ "strings"
+)
+
+// SVG defines the location of the generated SVG
+type SVG struct {
+ Writer io.Writer
+}
+
+// Offcolor defines the offset and color for gradients
+type Offcolor struct {
+ Offset uint8
+ Color string
+ Opacity float64
+}
+
+// Filterspec defines the specification of SVG filters
+type Filterspec struct {
+ In, In2, Result string
+}
+
+const (
+ svgtop = `
+
+") }
+
+// Script defines a script with a specified type, (for example "application/javascript").
+// if the first variadic argument is a link, use only the link reference.
+// Otherwise, treat those arguments as the text of the script (marked up as CDATA).
+// if no data is specified, just close the script element
+func (svg *SVG) Script(scriptype string, data ...string) {
+ svg.printf(`\n", href(data[0]))
+
+ case len(data) > 0:
+ svg.printf(">\n\n\n")
+
+ default:
+ svg.println(`/>`)
+ }
+}
+
+// Gstyle begins a group, with the specified style.
+// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#GElement
+func (svg *SVG) Gstyle(s string) { svg.println(group("style", s)) }
+
+// Gtransform begins a group, with the specified transform
+// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute
+func (svg *SVG) Gtransform(s string) { svg.println(group("transform", s)) }
+
+// Translate begins coordinate translation, end with Gend()
+// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute
+func (svg *SVG) Translate(x, y int) { svg.Gtransform(translate(x, y)) }
+
+// Scale scales the coordinate system by n, end with Gend()
+// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute
+func (svg *SVG) Scale(n float64) { svg.Gtransform(scale(n)) }
+
+// ScaleXY scales the coordinate system by dx and dy, end with Gend()
+// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute
+func (svg *SVG) ScaleXY(dx, dy float64) { svg.Gtransform(scaleXY(dx, dy)) }
+
+// SkewX skews the x coordinate system by angle a, end with Gend()
+// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute
+func (svg *SVG) SkewX(a float64) { svg.Gtransform(skewX(a)) }
+
+// SkewY skews the y coordinate system by angle a, end with Gend()
+// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute
+func (svg *SVG) SkewY(a float64) { svg.Gtransform(skewY(a)) }
+
+// SkewXY skews x and y coordinates by ax, ay respectively, end with Gend()
+// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute
+func (svg *SVG) SkewXY(ax, ay float64) { svg.Gtransform(skewX(ax) + " " + skewY(ay)) }
+
+// Rotate rotates the coordinate system by r degrees, end with Gend()
+// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute
+func (svg *SVG) Rotate(r float64) { svg.Gtransform(rotate(r)) }
+
+// TranslateRotate translates the coordinate system to (x,y), then rotates to r degrees, end with Gend()
+func (svg *SVG) TranslateRotate(x, y int, r float64) {
+ svg.Gtransform(translate(x, y) + " " + rotate(r))
+}
+
+// RotateTranslate rotates the coordinate system r degrees, then translates to (x,y), end with Gend()
+func (svg *SVG) RotateTranslate(x, y int, r float64) {
+ svg.Gtransform(rotate(r) + " " + translate(x, y))
+}
+
+// Group begins a group with arbitrary attributes
+func (svg *SVG) Group(s ...string) { svg.printf("`)) }
+
+// Gid begins a group, with the specified id
+func (svg *SVG) Gid(s string) {
+ svg.print(``)
+}
+
+// Gend ends a group (must be paired with Gsttyle, Gtransform, Gid).
+func (svg *SVG) Gend() { svg.println(``) }
+
+// ClipPath defines a clip path
+func (svg *SVG) ClipPath(s ...string) { svg.printf(``)) }
+
+// ClipEnd ends a ClipPath
+func (svg *SVG) ClipEnd() {
+ svg.println(``)
+}
+
+// Def begins a defintion block.
+// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#DefsElement
+func (svg *SVG) Def() { svg.println(``) }
+
+// DefEnd ends a defintion block.
+func (svg *SVG) DefEnd() { svg.println(``) }
+
+// Marker defines a marker
+// Standard reference: http://www.w3.org/TR/SVG11/painting.html#MarkerElement
+func (svg *SVG) Marker(id string, x, y, width, height int, s ...string) {
+ svg.printf(`\n"))
+}
+
+// MarkEnd ends a marker
+func (svg *SVG) MarkerEnd() { svg.println(``) }
+
+// Pattern defines a pattern with the specified dimensions.
+// The putype can be either "user" or "obj", which sets the patternUnits
+// attribute to be either userSpaceOnUse or objectBoundingBox
+// Standard reference: http://www.w3.org/TR/SVG11/pservers.html#Patterns
+func (svg *SVG) Pattern(id string, x, y, width, height int, putype string, s ...string) {
+ puattr := "userSpaceOnUse"
+ if putype != "user" {
+ puattr = "objectBoundingBox"
+ }
+ svg.printf(`\n"))
+}
+
+// PatternEnd ends a marker
+func (svg *SVG) PatternEnd() { svg.println(``) }
+
+// Desc specified the text of the description tag.
+// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#DescElement
+func (svg *SVG) Desc(s string) { svg.tt("desc", s) }
+
+// Title specified the text of the title tag.
+// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#TitleElement
+func (svg *SVG) Title(s string) { svg.tt("title", s) }
+
+// Link begins a link named "name", with the specified title.
+// Standard Reference: http://www.w3.org/TR/SVG11/linking.html#Links
+func (svg *SVG) Link(href string, title string) {
+ svg.printf("")
+}
+
+// LinkEnd ends a link.
+func (svg *SVG) LinkEnd() { svg.println(``) }
+
+// Use places the object referenced at link at the location x, y, with optional style.
+// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#UseElement
+func (svg *SVG) Use(x int, y int, link string, s ...string) {
+ svg.printf(`