diff --git a/cgraph/attribute.go b/cgraph/attribute.go index 753b0bb..2b66b2d 100644 --- a/cgraph/attribute.go +++ b/cgraph/attribute.go @@ -698,6 +698,10 @@ func (n *Node) SetFillColor(v string) *Node { return n } +func (n *Node) FixedSize() bool { + return n.GetStr(string(fixedSizeAttr)) == toBoolString(true) +} + // SetFixedSize // If false, the size of a node is determined by smallest width and height needed to contain its label and image, // if any, with a margin specified by the margin attribute. @@ -1045,14 +1049,32 @@ func (n *Node) SetImagePos(v ImagePos) *Node { return n } +type ImageScale string + +const ( + ImageScaleDefault ImageScale = "false" + ImageScaleTrue ImageScale = "true" + ImageScaleWidth ImageScale = "width" + ImageScaleHeight ImageScale = "height" + ImageScaleBoth ImageScale = "both" +) + +func (n *Node) ImageScale() ImageScale { + v := n.GetStr(string(imageScaleAttr)) + if v == "" { + return ImageScaleDefault + } + return ImageScale(v) +} + // SetImageScale // Attribute controlling how an image fills its containing node. In general, the image is given its natural size, (cf. dpi), and the node size is made large enough to contain its image, its label, its margin, and its peripheries. Its width and height will also be at least as large as its minimum width and height. If, however, fixedsize=true, the width and height attributes specify the exact size of the node. // During rendering, in the default case (imagescale=false), the image retains its natural size. If imagescale=true, the image is uniformly scaled (i.e., its aspect ratio is preserved) to fit inside the node. At least one dimension of the image will be as large as possible given the size of the node. When imagescale=width, the width of the image is scaled to fill the node width. The corresponding property holds when imagescale=height. When imagescale=both, both the height and the width are scaled separately to fill the node. // // In all cases, if a dimension of the image is larger than the corresponding dimension of the node, that dimension of the image is scaled down to fit the node. As with the case of expansion, if imagescale=true, width and height are scaled uniformly. // https://graphviz.gitlab.io/_pages/doc/info/attrs.html#a:imagescale -func (n *Node) SetImageScale(v bool) *Node { - n.SafeSet(string(imageScaleAttr), toBoolString(v), falseStr) +func (n *Node) SetImageScale(v ImageScale) *Node { + n.SafeSet(string(imageScaleAttr), string(v), string(ImageScaleDefault)) return n } @@ -1072,7 +1094,7 @@ func (g *Graph) SetInputScale(v float64) *Graph { } // Label returns label attribute. -func (g *Graph) Label() (string, error) { +func (g *Graph) Label() string { return g.GetStr(string(labelAttr)) } @@ -1090,7 +1112,7 @@ func (g *Graph) SetLabel(v string) *Graph { } // Label returns label attribute. -func (n *Node) Label() (string, error) { +func (n *Node) Label() string { return n.GetStr(string(labelAttr)) } @@ -1108,7 +1130,7 @@ func (n *Node) SetLabel(v string) *Node { } // Label returns label attribute. -func (e *Edge) Label() (string, error) { +func (e *Edge) Label() string { return e.GetStr(string(labelAttr)) } diff --git a/cgraph/cgraph.go b/cgraph/cgraph.go index b7b3146..591cb80 100644 --- a/cgraph/cgraph.go +++ b/cgraph/cgraph.go @@ -355,11 +355,7 @@ func setupNodeLabelIfEmpty(g *Graph) error { } func setLabelIfEmpty(n *Node) error { - label, err := n.Label() - if err != nil { - return err - } - if label == "" { + if n.Label() == "" { n.SetLabel("\\N") } return nil @@ -822,8 +818,9 @@ func (g *Graph) DeleteRecord(name string) error { return toError(res) } -func (g *Graph) GetStr(name string) (string, error) { - return wasm.GetStr(context.Background(), g.wasm, name) +func (g *Graph) GetStr(name string) string { + v, _ := wasm.GetStr(context.Background(), g.wasm, name) + return v } func (g *Graph) SymbolName(sym *Symbol) (string, error) { @@ -1281,8 +1278,9 @@ func (n *Node) DeleteRecord(name string) error { return toError(res) } -func (n *Node) GetStr(name string) (string, error) { - return wasm.GetStr(context.Background(), n.wasm, name) +func (n *Node) GetStr(name string) string { + v, _ := wasm.GetStr(context.Background(), n.wasm, name) + return v } func (n *Node) SymbolName(sym *Symbol) (string, error) { @@ -1364,8 +1362,9 @@ func (e *Edge) DeleteRecord(name string) error { return toError(res) } -func (e *Edge) GetStr(name string) (string, error) { - return wasm.GetStr(context.Background(), e.wasm, name) +func (e *Edge) GetStr(name string) string { + v, _ := wasm.GetStr(context.Background(), e.wasm, name) + return v } func (e *Edge) SymbolName(sym *Symbol) (string, error) { diff --git a/cmd/dot/go.mod b/cmd/dot/go.mod index c9f1d80..0456c89 100644 --- a/cmd/dot/go.mod +++ b/cmd/dot/go.mod @@ -11,6 +11,7 @@ require ( ) require ( + github.com/disintegration/imaging v1.6.2 // indirect github.com/flopp/go-findfont v0.1.0 // indirect github.com/fogleman/gg v1.3.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect diff --git a/cmd/dot/go.sum b/cmd/dot/go.sum index f6bda59..0d3b200 100644 --- a/cmd/dot/go.sum +++ b/cmd/dot/go.sum @@ -1,5 +1,7 @@ github.com/corona10/goimagehash v1.1.0 h1:teNMX/1e+Wn/AYSbLHX8mj+mF9r60R1kBeqE9MkoYwI= github.com/corona10/goimagehash v1.1.0/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/flopp/go-findfont v0.1.0 h1:lPn0BymDUtJo+ZkV01VS3661HL6F4qFlkhcJN55u6mU= github.com/flopp/go-findfont v0.1.0/go.mod h1:wKKxRDjD024Rh7VMwoU90i6ikQRCr+JTHB5n4Ejkqvw= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= @@ -12,11 +14,13 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550= github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= diff --git a/go.mod b/go.mod index a07c4bc..a6d9602 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22.0 require ( github.com/corona10/goimagehash v1.1.0 + github.com/disintegration/imaging v1.6.2 github.com/flopp/go-findfont v0.1.0 github.com/fogleman/gg v1.3.0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 diff --git a/go.sum b/go.sum index f7a2665..893f7a7 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/corona10/goimagehash v1.1.0 h1:teNMX/1e+Wn/AYSbLHX8mj+mF9r60R1kBeqE9MkoYwI= github.com/corona10/goimagehash v1.1.0/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/flopp/go-findfont v0.1.0 h1:lPn0BymDUtJo+ZkV01VS3661HL6F4qFlkhcJN55u6mU= github.com/flopp/go-findfont v0.1.0/go.mod h1:wKKxRDjD024Rh7VMwoU90i6ikQRCr+JTHB5n4Ejkqvw= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= @@ -10,7 +12,9 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550= github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= diff --git a/gvc/image_renderer.go b/gvc/image_renderer.go index b6a1294..2a88f77 100644 --- a/gvc/image_renderer.go +++ b/gvc/image_renderer.go @@ -11,6 +11,7 @@ import ( "strings" "sync" + "github.com/disintegration/imaging" "github.com/flopp/go-findfont" "github.com/fogleman/gg" "github.com/goccy/go-graphviz/internal/wasm" @@ -19,6 +20,8 @@ import ( "golang.org/x/image/font/gofont/goregular" "golang.org/x/image/font/opentype" "golang.org/x/image/font/sfnt" + + "github.com/goccy/go-graphviz/cgraph" ) var ( @@ -362,6 +365,12 @@ func (r *ImageRenderer) BezierCurve(ctx context.Context, job *Job, a []*PointFlo return nil } +const ( + defaultGAP = 4 + defaultXPAD = 4 * defaultGAP + defaultYPAD = 2 * defaultGAP +) + func (r *ImageRenderer) LoadImage(ctx context.Context, job *Job, shape *UserShape, bf *BoxFloat, filled bool) error { r.ctx.Push() defer r.ctx.Pop() @@ -377,7 +386,27 @@ func (r *ImageRenderer) LoadImage(ctx context.Context, job *Job, shape *UserShap if err != nil { return err } - r.ctx.DrawImageAnchored(img, int(job.Scale().X()*bf.LL().X()), -int(job.Scale().Y()*bf.LL().Y()), 0, 1) + topLeftX := bf.LL().X() + topLeftY := bf.LL().Y() + node := job.Object().Node() + if node != nil { + if node.FixedSize() || node.ImageScale() != cgraph.ImageScaleDefault { + bottomRightX := bf.UR().X() + bottomRightY := bf.UR().Y() + width := bottomRightX - topLeftX + height := bottomRightY - topLeftY + img = imaging.Resize(img, int(width), int(height), imaging.Lanczos) + xPAD := defaultXPAD / 2.0 + yPAD := defaultYPAD / 2.0 + posX := (topLeftX + xPAD) * job.Scale().X() + posY := (topLeftY + yPAD) * job.Scale().Y() + r.ctx.DrawImageAnchored(img, int(posX), -int(posY), 0, 1) + return nil + } + } + posX := topLeftX * job.Scale().X() + posY := topLeftY * job.Scale().Y() + r.ctx.DrawImageAnchored(img, int(posX), -int(posY), 0, 1) return nil } diff --git a/gvc/link.go b/gvc/link.go index c3d8c57..6b7fcf9 100644 --- a/gvc/link.go +++ b/gvc/link.go @@ -8,9 +8,18 @@ import ( "github.com/goccy/go-graphviz/internal/wasm" ) +//go:linkname toGraph github.com/goccy/go-graphviz/cgraph.toGraph +func toGraph(*wasm.Graph) *cgraph.Graph + //go:linkname toGraphWasm github.com/goccy/go-graphviz/cgraph.toGraphWasm func toGraphWasm(*cgraph.Graph) *wasm.Graph +//go:linkname toNode github.com/goccy/go-graphviz/cgraph.toNode +func toNode(*wasm.Node) *cgraph.Node + +//go:linkname toEdge github.com/goccy/go-graphviz/cgraph.toEdge +func toEdge(*wasm.Edge) *cgraph.Edge + //go:linkname toDictLink github.com/goccy/go-graphviz/cdt.toLink func toDictLink(*wasm.DictLink) *cdt.Link diff --git a/gvc/render_plugin.go b/gvc/render_plugin.go index e40c88b..9432140 100644 --- a/gvc/render_plugin.go +++ b/gvc/render_plugin.go @@ -4,6 +4,7 @@ import ( "context" "github.com/goccy/go-graphviz/cdt" + "github.com/goccy/go-graphviz/cgraph" "github.com/goccy/go-graphviz/internal/wasm" ) @@ -1005,6 +1006,35 @@ func (s *ObjectState) RawStyle() []string { return s.wasm.GetRawstyle() } +func (s *ObjectState) Type() ObjectType { + return ObjectType(s.wasm.GetType()) +} + +func (s *ObjectState) SetType(v ObjectType) { + s.wasm.SetType(wasm.ObjectType(v)) +} + +func (s *ObjectState) Graph() *cgraph.Graph { + return toGraph(s.wasm.GetG()) +} + +func (s *ObjectState) Node() *cgraph.Node { + return toNode(s.wasm.GetN()) +} + +func (s *ObjectState) Edge() *cgraph.Edge { + return toEdge(s.wasm.GetE()) +} + +type ObjectType int + +var ( + RootGraphObjectType ObjectType = ObjectType(wasm.ROOTGRAPH_OBJTYPE) + ClusterObjectType ObjectType = ObjectType(wasm.CLUSTER_OBJTYPE) + NodeObjectType ObjectType = ObjectType(wasm.NODE_OBJTYPE) + EdgeObjectType ObjectType = ObjectType(wasm.EDGE_OBJTYPE) +) + type Color struct { wasm *wasm.Color }