Skip to content

Commit

Permalink
Support image (#96)
Browse files Browse the repository at this point in the history
* support image rendering

* support fixedsize attr

* add logo.png
  • Loading branch information
goccy authored Oct 23, 2024
1 parent 2236280 commit 9780c71
Show file tree
Hide file tree
Showing 25 changed files with 718 additions and 127 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
*.jpg
.DS_Store
bin
internal/wasm/build/graphviz
12 changes: 12 additions & 0 deletions cdt/link.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package cdt

import (
"github.com/goccy/go-graphviz/internal/wasm"
)

func toLinkWasm(v *Link) *wasm.DictLink {
if v == nil {
return nil
}
return v.wasm
}
32 changes: 27 additions & 5 deletions cgraph/attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}

Expand All @@ -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))
}

Expand All @@ -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))
}

Expand All @@ -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))
}

Expand Down
60 changes: 52 additions & 8 deletions cgraph/cgraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,11 @@ func ParseBytes(bytes []byte) (*Graph, error) {
if graph == nil {
return nil, lastError()
}
return toGraph(graph), nil
g := toGraph(graph)
if err := setupNodeLabelIfEmpty(g); err != nil {
return nil, err
}
return g, nil
}

func ParseFile(path string) (*Graph, error) {
Expand All @@ -317,7 +321,44 @@ func Open(name string, desc *Desc, disc *Disc) (*Graph, error) {
if graph == nil {
return nil, lastError()
}
return toGraph(graph), nil
g := toGraph(graph)
if err := setupNodeLabelIfEmpty(g); err != nil {
return nil, err
}
return g, nil
}

func setupNodeLabelIfEmpty(g *Graph) error {
n, err := g.FirstNode()
if err != nil {
return err
}
if n == nil {
return nil
}
if err := setLabelIfEmpty(n); err != nil {
return err
}
for {
n, err = g.NextNode(n)
if err != nil {
return err
}
if n == nil {
break
}
if err := setLabelIfEmpty(n); err != nil {
return err
}
}
return nil
}

func setLabelIfEmpty(n *Node) error {
if n.Label() == "" {
n.SetLabel("\\N")
}
return nil
}

type ObjectTag int
Expand Down Expand Up @@ -777,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) {
Expand Down Expand Up @@ -1236,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) {
Expand Down Expand Up @@ -1319,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) {
Expand Down
1 change: 1 addition & 0 deletions cmd/dot/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions cmd/dot/go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand All @@ -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=
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand All @@ -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=
6 changes: 6 additions & 0 deletions graphviz.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"context"
"image"
"io"
"io/fs"

"github.com/goccy/go-graphviz/cgraph"
"github.com/goccy/go-graphviz/gvc"
"github.com/goccy/go-graphviz/internal/wasm"
)

type Graphviz struct {
Expand Down Expand Up @@ -133,3 +135,7 @@ func (g *Graphviz) Graph(option ...GraphOption) (*Graph, error) {
}
return graph, nil
}

func SetFileSystem(fs fs.FS) {
wasm.SetWasmFileSystem(fs)
}
55 changes: 55 additions & 0 deletions graphviz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package graphviz_test
import (
"bytes"
"context"
"embed"
"io/fs"
"os"
"path/filepath"
"testing"

"github.com/goccy/go-graphviz"
Expand Down Expand Up @@ -160,6 +163,58 @@ func TestParseFile(t *testing.T) {
}
}

//go:embed testdata/logo.png
var logoFS embed.FS

type imageFS struct{}

func (fs *imageFS) Open(name string) (fs.File, error) {
return logoFS.Open(filepath.Join("testdata", name))
}

func TestImageRender(t *testing.T) {
ctx := context.Background()
graphviz.SetFileSystem(new(imageFS))

g, err := graphviz.New(ctx)
if err != nil {
t.Fatal(err)
}
graph, err := g.Graph()
if err != nil {
t.Fatalf("%+v", err)
}
defer func() {
graph.Close()
g.Close()
}()
n, err := graph.CreateNodeByName("n")
if err != nil {
t.Fatalf("%+v", err)
}
n.SetLabel("")

// specify dummy image path.
// Normally, a path to `testdata` would be required before `logo.png`,
// but we confirm that the image can be loaded by appending the path to `testdata` within the `imageFS` specified by graphviz.SetFileSystem function.
// This test is to verify that images can be loaded relative to the specified FileSystem.
n.SetImage("logo.png")
m, err := graph.CreateNodeByName("m")
if err != nil {
t.Fatalf("%+v", err)
}
if _, err := graph.CreateEdgeByName("e", n, m); err != nil {
t.Fatalf("%+v", err)
}
var buf bytes.Buffer
if err := g.Render(ctx, graph, "png", &buf); err != nil {
t.Fatal(err)
}
if len(buf.Bytes()) == 0 {
t.Fatal("failed to render image")
}
}

func TestNodeDegree(t *testing.T) {
type test struct {
nodeName string
Expand Down
39 changes: 1 addition & 38 deletions gvc/gvc.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ func (c *Context) Close() error {
}

func (c *Context) Layout(ctx context.Context, g *cgraph.Graph, engine string) error {
if err := c.setupNodeLabelIfEmpty(g); err != nil {
return err
}
res, err := c.gvc.Layout(ctx, toGraphWasm(g), engine)
if err != nil {
return err
Expand Down Expand Up @@ -114,40 +111,6 @@ func (c *Context) FreeClonedContext(ctx context.Context) error {
return c.gvc.FreeClonedContext(ctx)
}

func (c *Context) setupNodeLabelIfEmpty(g *cgraph.Graph) error {
n, err := g.FirstNode()
if err != nil {
return err
}
if err := c.setLabelIfEmpty(n); err != nil {
return err
}
for {
n, err = g.NextNode(n)
if err != nil {
return err
}
if n == nil {
break
}
if err := c.setLabelIfEmpty(n); err != nil {
return err
}
}
return nil
}

func (c *Context) setLabelIfEmpty(n *cgraph.Node) error {
label, err := n.Label()
if err != nil {
return err
}
if label == "" {
n.SetLabel("\\N")
}
return nil
}

func newPlugins(ctx context.Context, plugins ...Plugin) ([]*wasm.SymList, error) {
defaults, err := wasm.DefaultSymList(ctx)
if err != nil {
Expand Down Expand Up @@ -190,7 +153,7 @@ func newPlugins(ctx context.Context, plugins ...Plugin) ([]*wasm.SymList, error)
if err != nil {
return nil, err
}
return append(defaults, sym, symTerm), nil
return append(append([]*wasm.SymList{sym}, defaults...), symTerm), nil
}

func toError(result int) error {
Expand Down
Loading

0 comments on commit 9780c71

Please sign in to comment.