Do minor refactoring of svg context
This commit is contained in:
parent
484fe1caef
commit
d297a025cd
4 changed files with 119 additions and 107 deletions
91
draw2dsvg/converters.go
Normal file
91
draw2dsvg/converters.go
Normal file
|
@ -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, " ")
|
||||||
|
}
|
122
draw2dsvg/gc.go
122
draw2dsvg/gc.go
|
@ -5,13 +5,9 @@ package draw2dsvg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"github.com/llgcode/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
"github.com/llgcode/draw2d/draw2dbase"
|
"github.com/llgcode/draw2d/draw2dbase"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
|
||||||
"math"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const ()
|
const ()
|
||||||
|
@ -28,7 +24,10 @@ const (
|
||||||
type SVG bytes.Buffer
|
type SVG bytes.Buffer
|
||||||
|
|
||||||
func NewSvg() *Svg {
|
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
|
// GraphicContext implements the draw2d.GraphicContext interface
|
||||||
|
@ -74,109 +73,30 @@ func (gc *GraphicContext) drawPaths(drawType drawType, paths ...*draw2d.Path) {
|
||||||
|
|
||||||
svgPaths := make([]Path, len(paths))
|
svgPaths := make([]Path, len(paths))
|
||||||
|
|
||||||
|
group := Group{
|
||||||
|
Paths: svgPaths,
|
||||||
|
}
|
||||||
|
|
||||||
for i, path := range paths {
|
for i, path := range paths {
|
||||||
svgPaths[i].Desc = toSvgPathDesc(path)
|
svgPaths[i].Desc = toSvgPathDesc(path)
|
||||||
if drawType&stroked == stroked {
|
}
|
||||||
svgPaths[i].Stroke = toSvgRGBA(gc.Current.StrokeColor)
|
|
||||||
svgPaths[i].StrokeWidth = toSvgLength(gc.Current.LineWidth)
|
if drawType&stroked == stroked {
|
||||||
svgPaths[i].StrokeLinecap = gc.Current.Cap.String()
|
group.Stroke = toSvgRGBA(gc.Current.StrokeColor)
|
||||||
svgPaths[i].StrokeLinejoin = gc.Current.Join.String()
|
group.StrokeWidth = toSvgLength(gc.Current.LineWidth)
|
||||||
if len(gc.Current.Dash) > 0 {
|
group.StrokeLinecap = gc.Current.Cap.String()
|
||||||
svgPaths[i].StrokeDasharray = toSvgArray(gc.Current.Dash)
|
group.StrokeLinejoin = gc.Current.Join.String()
|
||||||
svgPaths[i].StrokeDashoffset = toSvgLength(gc.Current.DashOffset)
|
if len(gc.Current.Dash) > 0 {
|
||||||
}
|
group.StrokeDasharray = toSvgArray(gc.Current.Dash)
|
||||||
} else {
|
group.StrokeDashoffset = toSvgLength(gc.Current.DashOffset)
|
||||||
svgPaths[i].Stroke = "none"
|
|
||||||
}
|
|
||||||
if drawType&filled == filled {
|
|
||||||
svgPaths[i].Fill = toSvgRGBA(gc.Current.FillColor)
|
|
||||||
} else {
|
|
||||||
svgPaths[i].Fill = "none"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gc.svg.Groups = append(gc.svg.Groups, Group{
|
if drawType&filled == filled {
|
||||||
Paths: svgPaths,
|
group.Fill = toSvgRGBA(gc.Current.FillColor)
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
return strings.Join(arr, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func toSvgPathDesc(p *draw2d.Path) string { // TODO move elsewhere
|
gc.svg.Groups = append(gc.svg.Groups, group)
|
||||||
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, " ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
|
|
|
@ -13,6 +13,7 @@ type Svg struct {
|
||||||
XMLName xml.Name `xml:"svg"`
|
XMLName xml.Name `xml:"svg"`
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
Groups []Group `xml:"g"`
|
Groups []Group `xml:"g"`
|
||||||
|
FillStroke
|
||||||
}
|
}
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
|
@ -30,7 +31,7 @@ type Path struct {
|
||||||
type Text struct {
|
type Text struct {
|
||||||
FillStroke
|
FillStroke
|
||||||
Text string `xml:",innerxml"`
|
Text string `xml:",innerxml"`
|
||||||
Style string `xml:",attr,omitempty"`
|
Style string `xml:"style,attr,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
/* shared attrs */
|
/* shared attrs */
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
package draw2dsvg
|
package draw2dsvg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test basic encoding of svg/xml elements
|
// Test basic encoding of svg/xml elements
|
||||||
|
@ -17,11 +17,11 @@ func TestXml(t *testing.T) {
|
||||||
svg := NewSvg()
|
svg := NewSvg()
|
||||||
svg.Groups = []Group{Group{
|
svg.Groups = []Group{Group{
|
||||||
Groups: []Group{
|
Groups: []Group{
|
||||||
Group{}, // nested groups
|
Group{}, // nested groups
|
||||||
Group{},
|
Group{},
|
||||||
},
|
},
|
||||||
Texts: []Text{
|
Texts: []Text{
|
||||||
Text{Text: "Hello"}, // text
|
Text{Text: "Hello"}, // text
|
||||||
Text{Text: "world", Style: "opacity: 0.5"}, // text with style
|
Text{Text: "world", Style: "opacity: 0.5"}, // text with style
|
||||||
},
|
},
|
||||||
Paths: []Path{
|
Paths: []Path{
|
||||||
|
@ -30,14 +30,14 @@ func TestXml(t *testing.T) {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
expectedOut := `<svg xmlns="http://www.w3.org/2000/svg">
|
expectedOut := `<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="none">
|
||||||
<g>
|
<g>
|
||||||
<g></g>
|
<g></g>
|
||||||
<g></g>
|
<g></g>
|
||||||
<path d="M100,200 C100,100 250,100 250,200 S400,300 400,200"></path>
|
<path d="M100,200 C100,100 250,100 250,200 S400,300 400,200"></path>
|
||||||
<path d=""></path>
|
<path d=""></path>
|
||||||
<text>Hello</text>
|
<text>Hello</text>
|
||||||
<text Style="opacity: 0.5">world</text>
|
<text style="opacity: 0.5">world</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>`
|
</svg>`
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue