diff --git a/draw2dsvg/converters.go b/draw2dsvg/converters.go new file mode 100644 index 0000000..1564f96 --- /dev/null +++ b/draw2dsvg/converters.go @@ -0,0 +1,91 @@ +// Copyright 2015 The draw2d Authors. All rights reserved. +// created: 16/12/2017 by Drahoslav Bednářpackage draw2dsvg + +package draw2dsvg + +import ( + "fmt" + "github.com/llgcode/draw2d" + "image/color" + "math" + "strings" +) + +func toSvgRGBA(c color.Color) string { + r, g, b, a := c.RGBA() + return fmt.Sprintf("rgba(%v, %v, %v, %.3f)", r>>8, g>>8, b>>8, float64(a>>8)/255) +} + +func toSvgLength(l float64) string { + return fmt.Sprintf("%.4f", l) +} + +func toSvgArray(nums []float64) string { + arr := make([]string, len(nums)) + for i, num := range nums { + arr[i] = fmt.Sprintf("%.4f", num) + } + return strings.Join(arr, ",") +} + +func toSvgPathDesc(p *draw2d.Path) string { + parts := make([]string, len(p.Components)) + ps := p.Points + for i, cmp := range p.Components { + switch cmp { + case draw2d.MoveToCmp: + parts[i] = fmt.Sprintf("M %.4f,%.4f", ps[0], ps[1]) + ps = ps[2:] + case draw2d.LineToCmp: + parts[i] = fmt.Sprintf("L %.4f,%.4f", ps[0], ps[1]) + ps = ps[2:] + case draw2d.QuadCurveToCmp: + parts[i] = fmt.Sprintf("Q %.4f,%.4f %.4f,%.4f", ps[0], ps[1], ps[2], ps[3]) + ps = ps[4:] + case draw2d.CubicCurveToCmp: + parts[i] = fmt.Sprintf("C %.4f,%.4f %.4f,%.4f %.4f,%.4f", ps[0], ps[1], ps[2], ps[3], ps[4], ps[5]) + ps = ps[6:] + case draw2d.ArcToCmp: + cx, cy := ps[0], ps[1] // center + rx, ry := ps[2], ps[3] // radii + fi := ps[4] + ps[5] // startAngle + angle + + // compute endpoint + sinfi, cosfi := math.Sincos(fi) + nom := math.Hypot(ry*cosfi, rx*sinfi) + x := cx + (rx*ry*cosfi)/nom + y := cy + (rx*ry*sinfi)/nom + + // compute large and sweep flags + large := 0 + sweep := 0 + if math.Abs(ps[5]) > math.Pi { + large = 1 + } + if !math.Signbit(ps[5]) { + sweep = 1 + } + // dirty hack to ensure whole arc is drawn + // if start point equals end point + if sweep == 1 { + x += 0.001 * sinfi + y += 0.001 * -cosfi + } else { + x += 0.001 * sinfi + y += 0.001 * cosfi + } + + // rx ry x-axis-rotation large-arc-flag sweep-flag x y + parts[i] = fmt.Sprintf("A %.4f %.4f %v %v %v %.4f %.4f", + rx, ry, + 0, + large, sweep, + x, y, + ) + ps = ps[6:] + case draw2d.CloseCmp: + parts[i] = "Z" + } + } + return strings.Join(parts, " ") +} diff --git a/draw2dsvg/gc.go b/draw2dsvg/gc.go index ccee40c..1d741b5 100644 --- a/draw2dsvg/gc.go +++ b/draw2dsvg/gc.go @@ -5,13 +5,9 @@ package draw2dsvg import ( "bytes" - "fmt" "github.com/llgcode/draw2d" "github.com/llgcode/draw2d/draw2dbase" "image" - "image/color" - "math" - "strings" ) const () @@ -28,7 +24,10 @@ const ( type SVG bytes.Buffer func NewSvg() *Svg { - return &Svg{Xmlns: "http://www.w3.org/2000/svg"} + return &Svg{ + Xmlns: "http://www.w3.org/2000/svg", + FillStroke: FillStroke{Fill: "none", Stroke: "none"}, + } } // GraphicContext implements the draw2d.GraphicContext interface @@ -74,109 +73,30 @@ func (gc *GraphicContext) drawPaths(drawType drawType, paths ...*draw2d.Path) { svgPaths := make([]Path, len(paths)) + group := Group{ + Paths: svgPaths, + } + for i, path := range paths { svgPaths[i].Desc = toSvgPathDesc(path) - if drawType&stroked == stroked { - svgPaths[i].Stroke = toSvgRGBA(gc.Current.StrokeColor) - svgPaths[i].StrokeWidth = toSvgLength(gc.Current.LineWidth) - svgPaths[i].StrokeLinecap = gc.Current.Cap.String() - svgPaths[i].StrokeLinejoin = gc.Current.Join.String() - if len(gc.Current.Dash) > 0 { - svgPaths[i].StrokeDasharray = toSvgArray(gc.Current.Dash) - svgPaths[i].StrokeDashoffset = toSvgLength(gc.Current.DashOffset) - } - } else { - svgPaths[i].Stroke = "none" - } - if drawType&filled == filled { - svgPaths[i].Fill = toSvgRGBA(gc.Current.FillColor) - } else { - svgPaths[i].Fill = "none" + } + + if drawType&stroked == stroked { + group.Stroke = toSvgRGBA(gc.Current.StrokeColor) + group.StrokeWidth = toSvgLength(gc.Current.LineWidth) + group.StrokeLinecap = gc.Current.Cap.String() + group.StrokeLinejoin = gc.Current.Join.String() + if len(gc.Current.Dash) > 0 { + group.StrokeDasharray = toSvgArray(gc.Current.Dash) + group.StrokeDashoffset = toSvgLength(gc.Current.DashOffset) } } - gc.svg.Groups = append(gc.svg.Groups, Group{ - Paths: svgPaths, - }) -} - -func toSvgRGBA(c color.Color) string { // TODO move elsewhere - r, g, b, a := c.RGBA() - return fmt.Sprintf("rgba(%v, %v, %v, %.3f)", r>>8, g>>8, b>>8, float64(a>>8)/255) -} - -func toSvgLength(l float64) string { - return fmt.Sprintf("%.4f", l) -} - -func toSvgArray(nums []float64) string { - arr := make([]string, len(nums)) - for i, num := range nums { - arr[i] = fmt.Sprintf("%.4f", num) + if drawType&filled == filled { + group.Fill = toSvgRGBA(gc.Current.FillColor) } - return strings.Join(arr, ",") -} -func toSvgPathDesc(p *draw2d.Path) string { // TODO move elsewhere - parts := make([]string, len(p.Components)) - ps := p.Points - for i, cmp := range p.Components { - switch cmp { - case draw2d.MoveToCmp: - parts[i] = fmt.Sprintf("M %.4f,%.4f", ps[0], ps[1]) - ps = ps[2:] - case draw2d.LineToCmp: - parts[i] = fmt.Sprintf("L %.4f,%.4f", ps[0], ps[1]) - ps = ps[2:] - case draw2d.QuadCurveToCmp: - parts[i] = fmt.Sprintf("Q %.4f,%.4f %.4f,%.4f", ps[0], ps[1], ps[2], ps[3]) - ps = ps[4:] - case draw2d.CubicCurveToCmp: - parts[i] = fmt.Sprintf("C %.4f,%.4f %.4f,%.4f %.4f,%.4f", ps[0], ps[1], ps[2], ps[3], ps[4], ps[5]) - ps = ps[6:] - case draw2d.ArcToCmp: - cx, cy := ps[0], ps[1] // center - rx, ry := ps[2], ps[3] // radii - fi := ps[4] + ps[5] // startAngle + angle - - // compute endpoint - sinfi, cosfi := math.Sincos(fi) - nom := math.Hypot(ry*cosfi, rx*sinfi) - x := cx + (rx*ry*cosfi)/nom - y := cy + (rx*ry*sinfi)/nom - - // compute large and sweep flags - large := 0 - sweep := 0 - if math.Abs(ps[5]) > math.Pi { - large = 1 - } - if !math.Signbit(ps[5]) { - sweep = 1 - } - // dirty hack to ensure whole arc is drawn - // if start point equals end point - if sweep == 1 { - x += 0.001 * sinfi - y += 0.001 * -cosfi - } else { - x += 0.001 * sinfi - y += 0.001 * cosfi - } - - // rx ry x-axis-rotation large-arc-flag sweep-flag x y - parts[i] = fmt.Sprintf("A %.4f %.4f %v %v %v %.4f %.4f", - rx, ry, - 0, - large, sweep, - x, y, - ) - ps = ps[6:] - case draw2d.CloseCmp: - parts[i] = "Z" - } - } - return strings.Join(parts, " ") + gc.svg.Groups = append(gc.svg.Groups, group) } /////////////////////////////////////// diff --git a/draw2dsvg/svg.go b/draw2dsvg/svg.go index 0913bdb..025847a 100644 --- a/draw2dsvg/svg.go +++ b/draw2dsvg/svg.go @@ -13,6 +13,7 @@ type Svg struct { XMLName xml.Name `xml:"svg"` Xmlns string `xml:"xmlns,attr"` Groups []Group `xml:"g"` + FillStroke } type Group struct { @@ -30,7 +31,7 @@ type Path struct { type Text struct { FillStroke Text string `xml:",innerxml"` - Style string `xml:",attr,omitempty"` + Style string `xml:"style,attr,omitempty"` } /* shared attrs */ diff --git a/draw2dsvg/xml_test.go b/draw2dsvg/xml_test.go index 0206a11..db22895 100644 --- a/draw2dsvg/xml_test.go +++ b/draw2dsvg/xml_test.go @@ -7,8 +7,8 @@ package draw2dsvg import ( - "testing" "encoding/xml" + "testing" ) // Test basic encoding of svg/xml elements @@ -17,11 +17,11 @@ func TestXml(t *testing.T) { svg := NewSvg() svg.Groups = []Group{Group{ Groups: []Group{ - Group{}, // nested groups + Group{}, // nested groups Group{}, }, Texts: []Text{ - Text{Text: "Hello"}, // text + Text{Text: "Hello"}, // text Text{Text: "world", Style: "opacity: 0.5"}, // text with style }, Paths: []Path{ @@ -30,14 +30,14 @@ func TestXml(t *testing.T) { }, }} - expectedOut := ` + expectedOut := ` Hello - world + world `