diff --git a/.gitignore b/.gitignore index 9fb747b..d30eea5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ *.jpg .DS_Store bin +internal/wasm/build/graphviz diff --git a/cdt/link.go b/cdt/link.go new file mode 100644 index 0000000..cdaa882 --- /dev/null +++ b/cdt/link.go @@ -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 +} 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 7e18880..591cb80 100644 --- a/cgraph/cgraph.go +++ b/cgraph/cgraph.go @@ -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) { @@ -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 @@ -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) { @@ -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) { @@ -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) { 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/graphviz.go b/graphviz.go index d20371d..0231cef 100644 --- a/graphviz.go +++ b/graphviz.go @@ -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 { @@ -133,3 +135,7 @@ func (g *Graphviz) Graph(option ...GraphOption) (*Graph, error) { } return graph, nil } + +func SetFileSystem(fs fs.FS) { + wasm.SetWasmFileSystem(fs) +} diff --git a/graphviz_test.go b/graphviz_test.go index c1de535..cadedc6 100644 --- a/graphviz_test.go +++ b/graphviz_test.go @@ -3,7 +3,10 @@ package graphviz_test import ( "bytes" "context" + "embed" + "io/fs" "os" + "path/filepath" "testing" "github.com/goccy/go-graphviz" @@ -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 diff --git a/gvc/gvc.go b/gvc/gvc.go index 37c11c0..d190f54 100644 --- a/gvc/gvc.go +++ b/gvc/gvc.go @@ -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 @@ -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 { @@ -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 { diff --git a/gvc/image_plugin.go b/gvc/image_plugin.go new file mode 100644 index 0000000..dab99f9 --- /dev/null +++ b/gvc/image_plugin.go @@ -0,0 +1,94 @@ +package gvc + +import ( + "context" + + "github.com/goccy/go-graphviz/internal/wasm" +) + +type LoadImagePlugin struct { + plugin *wasm.PluginAPI +} + +func (p *LoadImagePlugin) raw() *wasm.PluginAPI { + return p.plugin +} + +type loadImageConfig struct { + Type string + Engine LoadImageEngine +} + +type LoadImageEngine interface { + LoadImage(ctx context.Context, job *Job, shape *UserShape, bf *BoxFloat, filled bool) error +} + +type DefaultLoadImageEngine struct { +} + +func NewLoadImagePlugin(ctx context.Context, typ string, engine LoadImageEngine) (*LoadImagePlugin, error) { + cfg := defaultLoadImagePluginConfig(typ, engine) + return newLoadImagePlugin(ctx, cfg) +} + +func PNGLoadImagePlugin(ctx context.Context, engine LoadImageEngine) (*LoadImagePlugin, error) { + return NewLoadImagePlugin(ctx, "png:png", engine) +} + +func defaultLoadImagePluginConfig(typ string, engine LoadImageEngine) *loadImageConfig { + return &loadImageConfig{ + Type: typ, + Engine: engine, + } +} + +func newLoadImagePlugin(ctx context.Context, cfg *loadImageConfig) (*LoadImagePlugin, error) { + plg, err := wasm.NewPluginAPI(ctx) + if err != nil { + return nil, err + } + if err := plg.SetApi(wasm.API_LOADIMAGE); err != nil { + return nil, err + } + types, err := wasm.NewPluginInstalled(ctx) + if err != nil { + return nil, err + } + if err := types.SetType(cfg.Type); err != nil { + return nil, err + } + if err := types.SetQuality(1); err != nil { + return nil, err + } + engine, err := newLoadImageEngine(ctx, cfg.Engine) + if err != nil { + return nil, err + } + if err := types.SetEngine(engine); err != nil { + return nil, err + } + term, err := wasm.PluginInstalledZero(ctx) + if err != nil { + return nil, err + } + if err := plg.SetTypes([]*wasm.PluginInstalled{types, term}); err != nil { + return nil, err + } + return &LoadImagePlugin{ + plugin: plg, + }, nil +} + +func newLoadImageEngine(ctx context.Context, engine LoadImageEngine) (*wasm.LoadImageEngine, error) { + e, err := wasm.NewLoadImageEngine(ctx) + if err != nil { + return nil, err + } + ptr := wasm.WasmPtr(e) + if err := e.SetLoadImage(ctx, wasm.CreateCallbackFunc(func(ctx context.Context, job *wasm.Job, shape *wasm.UserShape, bf *wasm.BoxFloat, filled bool) error { + return engine.LoadImage(ctx, toJob(job), toUserShape(shape), toBoxFloat(bf), filled) + }, ptr)); err != nil { + return nil, err + } + return e, nil +} diff --git a/gvc/image_renderer.go b/gvc/image_renderer.go index f9e960d..2a88f77 100644 --- a/gvc/image_renderer.go +++ b/gvc/image_renderer.go @@ -4,19 +4,24 @@ import ( "bytes" "context" "fmt" + "image" "image/jpeg" "io" "os" "strings" "sync" + "github.com/disintegration/imaging" "github.com/flopp/go-findfont" "github.com/fogleman/gg" + "github.com/goccy/go-graphviz/internal/wasm" "github.com/golang/freetype/truetype" "golang.org/x/image/font" "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 ( @@ -360,6 +365,51 @@ 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() + + fs := wasm.FileSystem() + f, err := fs.Open(shape.Name()) + if err != nil { + return err + } + var buf bytes.Buffer + io.Copy(&buf, f) + img, _, err := image.Decode(&buf) + if err != nil { + return err + } + 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 +} + type FontLoader func(ctx context.Context, job *Job, font *TextFont) (font.Face, error) var ( diff --git a/gvc/init.go b/gvc/init.go index 2a77b23..197f48b 100644 --- a/gvc/init.go +++ b/gvc/init.go @@ -5,47 +5,57 @@ import ( ) func init() { - getEnginePtr := func(job *wasm.Job) uint64 { + getRenderEnginePtr := func(job *wasm.Job) uint64 { return job.GetGvc().GetApi()[wasm.API_RENDER].GetTypeptr().GetEngine().(uint64) } - wasm.Register_RenderEngine_BeginJob(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_EndJob(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_BeginGraph(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_EndGraph(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_BeginLayer(func(job *wasm.Job, _ string, _ int, _ int) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_EndLayer(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_BeginPage(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_EndPage(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_BeginCluster(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_EndCluster(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_BeginNodes(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_EndNodes(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_BeginEdges(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_EndEdges(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_BeginNode(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_EndNode(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_BeginEdge(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_EndEdge(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) + getLoadImageEnginePtr := func(job *wasm.Job) uint64 { + return job.GetGvc().GetApi()[wasm.API_LOADIMAGE].GetTypeptr().GetEngine().(uint64) + } + + wasm.Register_RenderEngine_BeginJob(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_EndJob(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_BeginGraph(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_EndGraph(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_BeginLayer(func(job *wasm.Job, _ string, _ int, _ int) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_EndLayer(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_BeginPage(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_EndPage(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_BeginCluster(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_EndCluster(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_BeginNodes(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_EndNodes(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_BeginEdges(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_EndEdges(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_BeginNode(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_EndNode(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_BeginEdge(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_EndEdge(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) wasm.Register_RenderEngine_BeginAnchor(func(job *wasm.Job, _ string, _ string, _ string, _ string) (uint64, error) { - return getEnginePtr(job), nil + return getRenderEnginePtr(job), nil }) - wasm.Register_RenderEngine_EndAnchor(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_BeginLabel(func(job *wasm.Job, _ wasm.LabelType) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_EndLabel(func(job *wasm.Job) (uint64, error) { return getEnginePtr(job), nil }) + wasm.Register_RenderEngine_EndAnchor(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_BeginLabel(func(job *wasm.Job, _ wasm.LabelType) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_EndLabel(func(job *wasm.Job) (uint64, error) { return getRenderEnginePtr(job), nil }) wasm.Register_RenderEngine_Textspan(func(job *wasm.Job, _ *wasm.PointFloat, _ *wasm.Textspan) (uint64, error) { - return getEnginePtr(job), nil + return getRenderEnginePtr(job), nil }) - wasm.Register_RenderEngine_ResolveColor(func(job *wasm.Job, _ *wasm.Color) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_Ellipse(func(job *wasm.Job, _ []*wasm.PointFloat, _ int) (uint64, error) { return getEnginePtr(job), nil }) + wasm.Register_RenderEngine_ResolveColor(func(job *wasm.Job, _ *wasm.Color) (uint64, error) { return getRenderEnginePtr(job), nil }) + wasm.Register_RenderEngine_Ellipse(func(job *wasm.Job, _ []*wasm.PointFloat, _ int) (uint64, error) { return getRenderEnginePtr(job), nil }) wasm.Register_RenderEngine_Polygon(func(job *wasm.Job, _ []*wasm.PointFloat, _ uint32, _ int) (uint64, error) { - return getEnginePtr(job), nil + return getRenderEnginePtr(job), nil }) wasm.Register_RenderEngine_Beziercurve(func(job *wasm.Job, _ []*wasm.PointFloat, _ uint32, _ int) (uint64, error) { - return getEnginePtr(job), nil + return getRenderEnginePtr(job), nil + }) + wasm.Register_RenderEngine_Polyline(func(job *wasm.Job, _ []*wasm.PointFloat, _ uint32) (uint64, error) { + return getRenderEnginePtr(job), nil }) - wasm.Register_RenderEngine_Polyline(func(job *wasm.Job, _ []*wasm.PointFloat, _ uint32) (uint64, error) { return getEnginePtr(job), nil }) - wasm.Register_RenderEngine_Comment(func(job *wasm.Job, _ string) (uint64, error) { return getEnginePtr(job), nil }) + wasm.Register_RenderEngine_Comment(func(job *wasm.Job, _ string) (uint64, error) { return getRenderEnginePtr(job), nil }) wasm.Register_RenderEngine_LibraryShape(func(job *wasm.Job, _ string, _ []*wasm.PointFloat, _ uint32, _ int) (uint64, error) { - return getEnginePtr(job), nil + return getRenderEnginePtr(job), nil + }) + + wasm.Register_LoadImageEngine_LoadImage(func(job *wasm.Job, shape *wasm.UserShape, bf *wasm.BoxFloat, filled bool) (uint64, error) { + return getLoadImageEnginePtr(job), nil }) } diff --git a/gvc/link.go b/gvc/link.go index 6ecafea..6b7fcf9 100644 --- a/gvc/link.go +++ b/gvc/link.go @@ -3,9 +3,25 @@ package gvc import ( _ "unsafe" + "github.com/goccy/go-graphviz/cdt" "github.com/goccy/go-graphviz/cgraph" "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 + +//go:linkname toDictLinkWasm github.com/goccy/go-graphviz/cdt.toLinkWasm +func toDictLinkWasm(*cdt.Link) *wasm.DictLink diff --git a/gvc/plugin.go b/gvc/plugin.go index 6527aff..08694ee 100644 --- a/gvc/plugin.go +++ b/gvc/plugin.go @@ -27,10 +27,15 @@ func DefaultPlugins(ctx context.Context) ([]Plugin, error) { if err != nil { return nil, err } + pngLoadImagePlugin, err := PNGLoadImagePlugin(ctx, pngRenderPlugin.RenderEngine()) + if err != nil { + return nil, err + } return []Plugin{ pngRenderPlugin, pngDevicePlugin, jpgRenderPlugin, jpgDevicePlugin, + pngLoadImagePlugin, }, nil } diff --git a/gvc/render_plugin.go b/gvc/render_plugin.go index 6af01f1..9432140 100644 --- a/gvc/render_plugin.go +++ b/gvc/render_plugin.go @@ -3,17 +3,24 @@ package gvc import ( "context" + "github.com/goccy/go-graphviz/cdt" + "github.com/goccy/go-graphviz/cgraph" "github.com/goccy/go-graphviz/internal/wasm" ) type RenderPlugin struct { plugin *wasm.PluginAPI + engine RenderEngine } func (p *RenderPlugin) raw() *wasm.PluginAPI { return p.plugin } +func (p *RenderPlugin) RenderEngine() RenderEngine { + return p.engine +} + type RenderEngine interface { BeginJob(ctx context.Context, job *Job) error EndJob(ctx context.Context, job *Job) error @@ -45,6 +52,7 @@ type RenderEngine interface { Polyline(ctx context.Context, job *Job, points []*PointFloat) error Comment(ctx context.Context, job *Job, comment string) error LibraryShape(ctx context.Context, job *Job, s string, points []*PointFloat, filled bool) error + LoadImage(ctx context.Context, job *Job, shape *UserShape, box *BoxFloat, filled bool) error } type DefaultRenderEngine struct { @@ -170,6 +178,10 @@ func (e *DefaultRenderEngine) LibraryShape(_ context.Context, _ *Job, _ string, return nil } +func (e *DefaultRenderEngine) LoadImage(_ context.Context, _ *Job, _ *UserShape, _ *BoxFloat, _ bool) error { + return nil +} + type RenderFeature int64 var ( @@ -320,6 +332,7 @@ func newRenderPlugin(ctx context.Context, cfg *renderConfig) (*RenderPlugin, err } return &RenderPlugin{ plugin: plg, + engine: cfg.RenderEngine, }, nil } @@ -527,6 +540,40 @@ func (j *Job) getWasm() *wasm.Job { return j.wasm } +type Point struct { + wasm *wasm.Point +} + +func toPoint(v *wasm.Point) *Point { + if v == nil { + return nil + } + return &Point{wasm: v} +} + +func (p *Point) getWasm() *wasm.Point { + if p == nil { + return nil + } + return p.wasm +} + +func (p *Point) X() int { + return int(p.wasm.GetX()) +} + +func (p *Point) SetX(x int) { + p.wasm.SetX(int64(x)) +} + +func (p *Point) Y() int { + return int(p.wasm.GetY()) +} + +func (p *Point) SetY(y int) { + p.wasm.SetY(int64(y)) +} + type PointFloat struct { wasm *wasm.PointFloat } @@ -959,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 } @@ -1036,3 +1112,220 @@ func (c *Color) Type() ColorType { func (c *Color) SetType(v ColorType) { c.wasm.SetType(wasm.ColorType(v)) } + +type UserShape struct { + wasm *wasm.UserShape +} + +func toUserShape(v *wasm.UserShape) *UserShape { + if v == nil { + return nil + } + return &UserShape{wasm: v} +} + +func (s *UserShape) getWasm() *wasm.UserShape { + if s == nil { + return nil + } + return s.wasm +} + +func (s *UserShape) Link() *cdt.Link { + return toDictLink(s.wasm.GetLink()) +} + +func (s *UserShape) SetLink(v *cdt.Link) { + s.wasm.SetLink(toDictLinkWasm(v)) +} + +func (s *UserShape) Name() string { + return s.wasm.GetName() +} + +func (s *UserShape) SetName(v string) { + s.wasm.SetName(v) +} + +func (s *UserShape) MacroID() int { + return int(s.wasm.GetMacroId()) +} + +func (s *UserShape) SetMacroID(v int) { + s.wasm.SetMacroId(int64(v)) +} + +func (s *UserShape) MustInline() bool { + return s.wasm.GetMustInline() +} + +func (s *UserShape) SetMustInline(v bool) { + s.wasm.SetMustInline(v) +} + +func (s *UserShape) NoCache() bool { + return s.wasm.GetNocache() +} + +func (s *UserShape) SetNoCache(v bool) { + s.wasm.SetNocache(v) +} + +func (s *UserShape) ImageType() ImageType { + return ImageType(s.wasm.GetType()) +} + +func (s *UserShape) SetImageType(v ImageType) { + s.wasm.SetType(wasm.ImageType(v)) +} + +func (s *UserShape) StringType() string { + return s.wasm.GetStringtype() +} + +func (s *UserShape) SetStringType(v string) { + s.wasm.SetStringtype(v) +} + +func (s *UserShape) X() int { + return int(s.wasm.GetX()) +} + +func (s *UserShape) SetX(v int) { + s.wasm.SetX(int64(v)) +} + +func (s *UserShape) Y() int { + return int(s.wasm.GetY()) +} + +func (s *UserShape) SetY(v int) { + s.wasm.SetY(int64(v)) +} + +func (s *UserShape) Width() int { + return int(s.wasm.GetW()) +} + +func (s *UserShape) SetWidth(v int) { + s.wasm.SetW(int64(v)) +} + +func (s *UserShape) Height() int { + return int(s.wasm.GetH()) +} + +func (s *UserShape) SetHeight(v int) { + s.wasm.SetH(int64(v)) +} + +func (s *UserShape) DPI() int { + return int(s.wasm.GetDpi()) +} + +func (s *UserShape) SetDPI(v int) { + s.wasm.SetDpi(int64(v)) +} + +func (s *UserShape) Data() []byte { + return s.wasm.GetData().([]byte) +} + +func (s *UserShape) SetData(v []byte) { + s.wasm.SetData(v) +} + +func (s *UserShape) DataSize() uint { + return uint(s.wasm.GetDatasize()) +} + +func (s *UserShape) SetDataSize(v uint) { + s.wasm.SetDatasize(uint64(v)) +} + +type ImageType int + +var ( + ImageTypeNull ImageType = ImageType(wasm.IMAGE_TYPE_NULL) + ImageTypeBMP ImageType = ImageType(wasm.IMAGE_TYPE_BMP) + ImageTypeGIF ImageType = ImageType(wasm.IMAGE_TYPE_GIF) + ImageTypePNG ImageType = ImageType(wasm.IMAGE_TYPE_PNG) + ImageTypeJPEG ImageType = ImageType(wasm.IMAGE_TYPE_JPEG) + ImageTypePDF ImageType = ImageType(wasm.IMAGE_TYPE_PDF) + ImageTypePS ImageType = ImageType(wasm.IMAGE_TYPE_PS) + ImageTypeEPS ImageType = ImageType(wasm.IMAGE_TYPE_EPS) + ImageTypeSVG ImageType = ImageType(wasm.IMAGE_TYPE_SVG) + ImageTypeXML ImageType = ImageType(wasm.IMAGE_TYPE_XML) + ImageTypeRIFF ImageType = ImageType(wasm.IMAGE_TYPE_RIFF) + ImageTypeWEBP ImageType = ImageType(wasm.IMAGE_TYPE_WEBP) + ImageTypeICO ImageType = ImageType(wasm.IMAGE_TYPE_ICO) + ImageTypeTIFF ImageType = ImageType(wasm.IMAGE_TYPE_TIFF) +) + +type Box struct { + wasm *wasm.Box +} + +func toBox(v *wasm.Box) *Box { + if v == nil { + return nil + } + return &Box{wasm: v} +} + +func (b *Box) getWasm() *wasm.Box { + if b == nil { + return nil + } + return b.wasm +} + +func (b *Box) LL() *Point { + return toPoint(b.wasm.GetLl()) +} + +func (b *Box) SetLL(v *Point) { + b.wasm.SetLl(v.getWasm()) +} + +func (b *Box) UR() *Point { + return toPoint(b.wasm.GetUr()) +} + +func (b *Box) SetUR(v *Point) { + b.wasm.SetUr(v.getWasm()) +} + +type BoxFloat struct { + wasm *wasm.BoxFloat +} + +func toBoxFloat(v *wasm.BoxFloat) *BoxFloat { + if v == nil { + return nil + } + return &BoxFloat{wasm: v} +} + +func (f *BoxFloat) getWasm() *wasm.BoxFloat { + if f == nil { + return nil + } + return f.wasm +} + +func (f *BoxFloat) LL() *PointFloat { + return toPointFloat(f.wasm.GetLl()) +} + +func (f *BoxFloat) SetLL(v *PointFloat) { + f.wasm.SetLl(v.getWasm()) +} + +func (f *BoxFloat) UR() *PointFloat { + return toPointFloat(f.wasm.GetUr()) +} + +func (f *BoxFloat) SetUR(v *PointFloat) { + f.wasm.SetUr(v.getWasm()) +} diff --git a/internal/tools/nori/templates/bind.go.tmpl b/internal/tools/nori/templates/bind.go.tmpl index 651f011..27e6375 100644 --- a/internal/tools/nori/templates/bind.go.tmpl +++ b/internal/tools/nori/templates/bind.go.tmpl @@ -6,6 +6,7 @@ import ( "context" _ "embed" "fmt" + "io/fs" "os" "path/filepath" "unsafe" @@ -24,10 +25,22 @@ var wasmFile []byte type WasmModule struct { mod api.Module + fs *WasmFileSystem lookupFuncMap *LookupFuncMap callbackFuncMap *CallbackFuncMap } +type WasmFileSystem struct { + subFS fs.FS +} + +func (fs *WasmFileSystem) Open(name string) (fs.File, error) { + if fs.subFS != nil { + return fs.subFS.Open(name) + } + return os.Open(name) +} + type LookupFuncMap struct { {{- range .ExportCallbackFunctions }} {{ .Name }} func({{- range .Args }}{{ .Value.GoType }},{{- end }}) (uint64, error) @@ -130,15 +143,12 @@ func init() { if err != nil { panic(err) } - dir, err := os.MkdirTemp("", "graphviz") - if err != nil { - panic(err) - } + fs := &WasmFileSystem{} m, err := r.InstantiateModule( ctx, compiled, wazero.NewModuleConfig(). - WithFSConfig(wazero.NewFSConfig().WithDirMount(dir, "/")). + WithFSConfig(wazero.NewFSConfig().WithFSMount(fs, "/")). WithStdout(os.Stdout). WithName("wasi"), ) @@ -147,6 +157,7 @@ func init() { } mod = &WasmModule{ mod: m, + fs: fs, lookupFuncMap: &LookupFuncMap{}, callbackFuncMap: &CallbackFuncMap{ {{- range .ExportCallbackFunctions }} diff --git a/internal/wasm/bind.go b/internal/wasm/bind.go index 3ee5ba2..216acb1 100644 --- a/internal/wasm/bind.go +++ b/internal/wasm/bind.go @@ -6,6 +6,7 @@ import ( "context" _ "embed" "fmt" + "io/fs" "os" "path/filepath" "reflect" @@ -24,10 +25,22 @@ var wasmFile []byte type WasmModule struct { mod api.Module + fs *WasmFileSystem lookupFuncMap *LookupFuncMap callbackFuncMap *CallbackFuncMap } +type WasmFileSystem struct { + subFS fs.FS +} + +func (fs *WasmFileSystem) Open(name string) (fs.File, error) { + if fs.subFS != nil { + return fs.subFS.Open(name) + } + return os.Open(name) +} + type LookupFuncMap struct { IDAllocator_Open func(*Graph, *ClientDiscipline) (uint64, error) IDAllocator_Map func(any, int, string, *uint64, int) (uint64, error) @@ -2784,15 +2797,12 @@ func init() { if err != nil { panic(err) } - dir, err := os.MkdirTemp("", "graphviz") - if err != nil { - panic(err) - } + fs := &WasmFileSystem{} m, err := r.InstantiateModule( ctx, compiled, wazero.NewModuleConfig(). - WithFSConfig(wazero.NewFSConfig().WithDirMount(dir, "/")). + WithFSConfig(wazero.NewFSConfig().WithFSMount(fs, "/")). WithStdout(os.Stdout). WithName("wasi"), ) @@ -2801,6 +2811,7 @@ func init() { } mod = &WasmModule{ mod: m, + fs: fs, lookupFuncMap: &LookupFuncMap{}, callbackFuncMap: &CallbackFuncMap{ IDAllocator_Open: make(map[uint64]func(context.Context, *Graph, *ClientDiscipline) (any, error)), diff --git a/internal/wasm/bind.proto b/internal/wasm/bind.proto index b9534a8..5f72dab 100644 --- a/internal/wasm/bind.proto +++ b/internal/wasm/bind.proto @@ -1639,12 +1639,12 @@ message Context { repeated PluginAvailable apis = 5 [(nori.field).type = { pointer: 1 array: true - array_num: 4 + array_num: 5 }]; repeated PluginAvailable api = 6 [(nori.field).type = { pointer: 1 array: true - array_num: 4 + array_num: 5 }]; } diff --git a/internal/wasm/build/bind.c b/internal/wasm/build/bind.c index 50299bd..17aa686 100644 --- a/internal/wasm/build/bind.c +++ b/internal/wasm/build/bind.c @@ -2473,7 +2473,7 @@ void wasm_bridge_set_Context_input_filenames(GVC_t *recv, GoSlice * v) { } void wasm_bridge_get_Context_apis(GVC_t *recv, GoSlice ** ret) {GoSlice *v = (GoSlice *)malloc(sizeof(GoSlice)); - int v_length = 4; + int v_length = 5; v->len = v_length; void **v_data = (void **)malloc(8 * v_length); v->data = v_data; @@ -2493,7 +2493,7 @@ void wasm_bridge_set_Context_apis(GVC_t *recv, GoSlice * v) { } void wasm_bridge_get_Context_api(GVC_t *recv, GoSlice ** ret) {GoSlice *v = (GoSlice *)malloc(sizeof(GoSlice)); - int v_length = 4; + int v_length = 5; v->len = v_length; void **v_data = (void **)malloc(8 * v_length); v->data = v_data; diff --git a/internal/wasm/ext.go b/internal/wasm/ext.go index 0bb64fc..9505f7f 100644 --- a/internal/wasm/ext.go +++ b/internal/wasm/ext.go @@ -2,6 +2,8 @@ package wasm import ( "context" + "io/fs" + "sync" ) func DefaultSymList(ctx context.Context) ([]*SymList, error) { @@ -68,35 +70,18 @@ func SymListZero(ctx context.Context) (*SymList, error) { return newSymList(ptr), nil } -type RenderEngineInterface interface { - BeginJob(context.Context, *Job) error - EndJob(context.Context, *Job) error - BeginGraph(context.Context, *Job) error - EndGraph(context.Context, *Job) error - BeginLayer(context.Context, *Job, string, int, int) error - EndLayer(context.Context, *Job) error - BeginPage(context.Context, *Job) error - EndPage(context.Context, *Job) error - BeginCluster(context.Context, *Job) error - EndCluster(context.Context, *Job) error - BeginNodes(context.Context, *Job) error - EndNodes(context.Context, *Job) error - BeginEdges(context.Context, *Job) error - EndEdges(context.Context, *Job) error - BeginNode(context.Context, *Job) error - EndNode(context.Context, *Job) error - BeginEdge(context.Context, *Job) error - EndEdge(context.Context, *Job) error - BeginAnchor(context.Context, *Job, string, string, string, string) error - EndAnchor(context.Context, *Job) error - BeginLabel(context.Context, *Job, LabelType) error - EndLabel(context.Context, *Job) error - Textspan(context.Context, *Job, *PointFloat, *Textspan) error - ResolveColor(context.Context, *Job, *Color) error - Ellipse(context.Context, *Job, []*PointFloat, int) error - Polygon(context.Context, *Job, []*PointFloat, int) error - Beziercurve(context.Context, *Job, []*PointFloat, int, int) error - Polyline(context.Context, *Job, []*PointFloat) error - Comment(context.Context, *Job, string) error - LibraryShape(context.Context, *Job, string, *PointFloat, int, int) error +var ( + fsMu sync.Mutex +) + +func SetWasmFileSystem(fs fs.FS) { + fsMu.Lock() + mod.fs.subFS = fs + fsMu.Unlock() +} + +func FileSystem() fs.FS { + fsMu.Lock() + defer fsMu.Unlock() + return mod.fs } diff --git a/internal/wasm/graphviz.wasm b/internal/wasm/graphviz.wasm index 6c10eb8..5476b24 100755 Binary files a/internal/wasm/graphviz.wasm and b/internal/wasm/graphviz.wasm differ diff --git a/testdata/.gitignore b/testdata/.gitignore new file mode 100644 index 0000000..6ebe739 --- /dev/null +++ b/testdata/.gitignore @@ -0,0 +1,3 @@ +!*.svg +!*.png +!*.jpg diff --git a/testdata/logo.png b/testdata/logo.png new file mode 100644 index 0000000..7358fe0 Binary files /dev/null and b/testdata/logo.png differ