// 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 = ` ` vbfmt = `viewBox="%d %d %d %d"` emptyclose = "/>\n" ) // New is the SVG constructor, specifying the io.Writer where the generated SVG is written. func New(w io.Writer) *SVG { return &SVG{w} } func (svg *SVG) print(a ...interface{}) (n int, errno error) { return fmt.Fprint(svg.Writer, a...) } func (svg *SVG) println(a ...interface{}) (n int, errno error) { return fmt.Fprintln(svg.Writer, a...) } func (svg *SVG) printf(format string, a ...interface{}) (n int, errno error) { return fmt.Fprintf(svg.Writer, format, a...) } func (svg *SVG) genattr(ns []string) { for _, v := range ns { svg.printf("\n %s", v) } svg.println(svgns) } // Structure, Metadata, Scripting, Style, Transformation, and Links // Start begins the SVG document with the width w and height h. // Other attributes may be optionally added, for example viewbox or additional namespaces // Standard Reference: http://www.w3.org/TR/SVG11/struct.html#SVGElement func (svg *SVG) Start(w int, h int, ns ...string) { svg.printf(svginitfmt, svgtop, w, "", h, "") svg.genattr(ns) } // Startunit begins the SVG document, with width and height in the specified units // Other attributes may be optionally added, for example viewbox or additional namespaces func (svg *SVG) Startunit(w int, h int, unit string, ns ...string) { svg.printf(svginitfmt, svgtop, w, unit, h, unit) svg.genattr(ns) } // Startpercent begins the SVG document, with width and height as percentages // Other attributes may be optionally added, for example viewbox or additional namespaces func (svg *SVG) Startpercent(w int, h int, ns ...string) { svg.printf(svginitfmt, svgtop, w, "%", h, "%") svg.genattr(ns) } // Startview begins the SVG document, with the specified width, height, and viewbox // Other attributes may be optionally added, for example viewbox or additional namespaces func (svg *SVG) Startview(w, h, minx, miny, vw, vh int) { svg.Start(w, h, fmt.Sprintf(vbfmt, minx, miny, vw, vh)) } func (svg *SVG) StartviewUnit(w, h int, unit string, minx, miny, vw, vh int) { svg.Startunit(w, h, unit, fmt.Sprintf(vbfmt, minx, miny, vw, vh)) } // Startraw begins the SVG document, passing arbitrary attributes func (svg *SVG) Startraw(ns ...string) { svg.printf(svgtop) svg.genattr(ns) } // End the SVG document func (svg *SVG) End() { svg.println("") } // linkembed defines an element with a specified type, // (for example "application/javascript", or "text/css"). // 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 element func (svg *SVG) linkembed(tag string, scriptype string, data ...string) { svg.printf(`<%s type="%s"`, tag, scriptype) switch { case len(data) == 1 && islink(data[0]): svg.printf(" %s/>\n", href(data[0])) case len(data) > 0: svg.printf(">\n\n\n", tag) default: svg.println(`/>`) } } // Script defines a script with a specified type, (for example "application/javascript"). func (svg *SVG) Script(scriptype string, data ...string) { svg.linkembed("script", scriptype, data...) } // Style defines the specified style (for example "text/css") func (svg *SVG) Style(scriptype string, data ...string) { svg.linkembed("style", scriptype, data...) } // 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(``)) } // MaskEnd ends a Mask. func (svg *SVG) MaskEnd() { svg.println(``) } // Shapes // Circle centered at x,y, with radius r, with optional style. // Standard Reference: http://www.w3.org/TR/SVG11/shapes.html#CircleElement func (svg *SVG) Circle(x int, y int, r int, s ...string) { svg.printf(`")) xml.Escape(svg.Writer, []byte(t)) svg.println(``) } // Textpath places text optionally styled text along a previously defined path // Standard Reference: http://www.w3.org/TR/SVG11/text.html#TextPathElement func (svg *SVG) Textpath(t string, pathid string, s ...string) { svg.printf("", endstyle(s, ">"), pathid) xml.Escape(svg.Writer, []byte(t)) svg.println(``) } // Textlines places a series of lines of text starting at x,y, at the specified size, fill, and alignment. // Each line is spaced according to the spacing argument func (svg *SVG) Textlines(x, y int, s []string, size, spacing int, fill, align string) { svg.Gstyle(fmt.Sprintf("font-size:%dpx;fill:%s;text-anchor:%s", size, fill, align)) for _, t := range s { svg.Text(x, y, t) y += spacing } svg.Gend() } // Colors // RGB specifies a fill color in terms of a (r)ed, (g)reen, (b)lue triple. // Standard reference: http://www.w3.org/TR/css3-color/ func (svg *SVG) RGB(r int, g int, b int) string { return fmt.Sprintf(`fill:rgb(%d,%d,%d)`, r, g, b) } // RGBA specifies a fill color in terms of a (r)ed, (g)reen, (b)lue triple and opacity. func (svg *SVG) RGBA(r int, g int, b int, a float64) string { return fmt.Sprintf(`fill-opacity:%.2f; %s`, a, svg.RGB(r, g, b)) } // Gradients // LinearGradient constructs a linear color gradient identified by id, // along the vector defined by (x1,y1), and (x2,y2). // The stop color sequence defined in sc. Coordinates are expressed as percentages. func (svg *SVG) LinearGradient(id string, x1, y1, x2, y2 uint8, sc []Offcolor) { svg.printf("\n", id, pct(x1), pct(y1), pct(x2), pct(y2)) svg.stopcolor(sc) svg.println("") } // RadialGradient constructs a radial color gradient identified by id, // centered at (cx,cy), with a radius of r. // (fx, fy) define the location of the focal point of the light source. // The stop color sequence defined in sc. // Coordinates are expressed as percentages. func (svg *SVG) RadialGradient(id string, cx, cy, r, fx, fy uint8, sc []Offcolor) { svg.printf("\n", id, pct(cx), pct(cy), pct(r), pct(fx), pct(fy)) svg.stopcolor(sc) svg.println("") } // stopcolor is a utility function used by the gradient functions // to define a sequence of offsets (expressed as percentages) and colors func (svg *SVG) stopcolor(oc []Offcolor) { for _, v := range oc { svg.printf("\n", pct(v.Offset), v.Color, v.Opacity) } } // Filter Effects: // Most functions have common attributes (in, in2, result) defined in type Filterspec // used as a common first argument. // Filter begins a filter set // Standard reference: http://www.w3.org/TR/SVG11/filters.html#FilterElement func (svg *SVG) Filter(id string, s ...string) { svg.printf(`\n")) } // Fend ends a filter set // Standard reference: http://www.w3.org/TR/SVG11/filters.html#FilterElement func (svg *SVG) Fend() { svg.println(``) } // FeBlend specifies a Blend filter primitive // Standard reference: http://www.w3.org/TR/SVG11/filters.html#feBlendElement func (svg *SVG) FeBlend(fs Filterspec, mode string, s ...string) { switch mode { case "normal", "multiply", "screen", "darken", "lighten": break default: mode = "normal" } svg.printf(` 360 { value = 0 } svg.printf(` 1 { value = 1 } svg.printf(``) } // FeCompEnd ends a feComponent filter element // Standard reference: http://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement func (svg *SVG) FeCompEnd() { svg.println(``) } // FeComposite specifies a feComposite filter primitive // Standard reference: http://www.w3.org/TR/SVG11/filters.html#feCompositeElement func (svg *SVG) FeComposite(fs Filterspec, operator string, k1, k2, k3, k4 int, s ...string) { switch operator { case "over", "in", "out", "atop", "xor", "arithmetic": break default: operator = "over" } svg.printf(``)) } // FeDiffEnd ends a diffuse lighting filter primitive container // Standard reference: http://www.w3.org/TR/SVG11/filters.html#feDiffuseLightingElement func (svg *SVG) FeDiffEnd() { svg.println(``) } // FeDisplacementMap specifies a feDisplacementMap filter primitive // Standard reference: http://www.w3.org/TR/SVG11/filters.html#feDisplacementMapElement func (svg *SVG) FeDisplacementMap(fs Filterspec, scale float64, xchannel, ychannel string, s ...string) { svg.printf(``) for _, n := range nodes { svg.printf("\n", n) } svg.println(``) } // FeMorphology specifies a feMorphologyLight filter primitive // Standard reference: http://www.w3.org/TR/SVG11/filters.html#feMorphologyElement func (svg *SVG) FeMorphology(fs Filterspec, operator string, xradius, yradius float64, s ...string) { switch operator { case "erode", "dilate": break default: operator = "erode" } svg.printf(`\n")) } // FeSpecEnd ends a specular lighting filter primitive container // Standard reference: http://www.w3.org/TR/SVG11/filters.html#feSpecularLightingElement func (svg *SVG) FeSpecEnd() { svg.println(``) } // FeSpotLight specifies a feSpotLight filter primitive // Standard reference: http://www.w3.org/TR/SVG11/filters.html#feSpotLightElement func (svg *SVG) FeSpotLight(fs Filterspec, x, y, z, px, py, pz float64, s ...string) { svg.printf(` 1 { bfx = 0 } if bfy < 0 || bfy > 1 { bfy = 0 } switch ftype[0:1] { case "f", "F": ftype = "fractalNoise" case "t", "T": ftype = "turbulence" default: ftype = "turbulence" } var ss string if stitch { ss = "stitch" } else { ss = "noStitch" } svg.printf(` 0 { svg.Gstyle(s[0]) } for ix := x; ix <= x+w; ix += n { svg.Line(ix, y, ix, y+h) } for iy := y; iy <= y+h; iy += n { svg.Line(x, iy, x+w, iy) } if len(s) > 0 { svg.Gend() } } // Support functions // style returns a style name,attribute string func style(s string) string { if len(s) > 0 { return fmt.Sprintf(`style="%s"`, s) } return s } // pp returns a series of polygon points func (svg *SVG) pp(x []int, y []int, tag string) { svg.print(tag) if len(x) != len(y) { svg.print(" ") return } lx := len(x) - 1 for i := 0; i < lx; i++ { svg.print(coord(x[i], y[i]) + " ") } svg.print(coord(x[lx], y[lx])) } // endstyle modifies an SVG object, with either a series of name="value" pairs, // or a single string containing a style func endstyle(s []string, endtag string) string { if len(s) > 0 { nv := "" for i := 0; i < len(s); i++ { if strings.Index(s[i], "=") > 0 { nv += (s[i]) + " " } else { nv += style(s[i]) + " " } } return nv + endtag } return endtag } // tt creates a xml element, tag containing s func (svg *SVG) tt(tag string, s string) { svg.print("<" + tag + ">") xml.Escape(svg.Writer, []byte(s)) svg.println("") } // poly compiles the polygon element func (svg *SVG) poly(x []int, y []int, tag string, s ...string) { svg.pp(x, y, "<"+tag+" points=\"") svg.print(`" ` + endstyle(s, "/>\n")) } // onezero returns "0" or "1" func onezero(flag bool) string { if flag { return "1" } return "0" } // pct returns a percetage, capped at 100 func pct(n uint8) uint8 { if n > 100 { return 100 } return n } // islink determines if a string is a script reference func islink(link string) bool { return strings.HasPrefix(link, "http://") || strings.HasPrefix(link, "#") || strings.HasPrefix(link, "../") || strings.HasPrefix(link, "./") } // group returns a group element func group(tag string, value string) string { return fmt.Sprintf(``, tag, value) } // scale return the scale string for the transform func scale(n float64) string { return fmt.Sprintf(`scale(%g)`, n) } // scaleXY return the scale string for the transform func scaleXY(dx, dy float64) string { return fmt.Sprintf(`scale(%g,%g)`, dx, dy) } // skewx returns the skewX string for the transform func skewX(angle float64) string { return fmt.Sprintf(`skewX(%g)`, angle) } // skewx returns the skewX string for the transform func skewY(angle float64) string { return fmt.Sprintf(`skewY(%g)`, angle) } // rotate returns the rotate string for the transform func rotate(r float64) string { return fmt.Sprintf(`rotate(%g)`, r) } // translate returns the translate string for the transform func translate(x, y int) string { return fmt.Sprintf(`translate(%d,%d)`, x, y) } // coord returns a coordinate string func coord(x int, y int) string { return fmt.Sprintf(`%d,%d`, x, y) } // ptag returns the beginning of the path element func ptag(x int, y int) string { return fmt.Sprintf(` 0 { attrs += fmt.Sprintf(`in="%s" `, s.In) } if len(s.In2) > 0 { attrs += fmt.Sprintf(`in2="%s" `, s.In2) } if len(s.Result) > 0 { attrs += fmt.Sprintf(`result="%s" `, s.Result) } return attrs } // tablevalues outputs a series of values as a XML attribute func (svg *SVG) tablevalues(s string, t []float64) { svg.printf(` %s="`, s) for i := 0; i < len(t)-1; i++ { svg.printf("%g ", t[i]) } svg.printf(`%g"%s`, t[len(t)-1], emptyclose) } // imgchannel validates the image channel indicator func imgchannel(c string) string { switch c { case "R", "G", "B", "A": return c case "r", "g", "b", "a": return strings.ToUpper(c) case "red", "green", "blue", "alpha": return strings.ToUpper(c[0:1]) case "Red", "Green", "Blue", "Alpha": return c[0:1] } return "R" }