compile and render functions

This commit is contained in:
Alexander Wang 2024-12-29 14:19:32 -07:00
parent 9e20ed816d
commit 3dee7bbdaf
No known key found for this signature in database
GPG key ID: BE3937D0D52D8927
10 changed files with 147 additions and 50 deletions

View file

@ -5,6 +5,7 @@ package d2wasm
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"runtime/debug"
"syscall/js" "syscall/js"
) )
@ -36,7 +37,7 @@ func wrapWASMCall(fn func(args []js.Value) (interface{}, error)) js.Func {
if r := recover(); r != nil { if r := recover(); r != nil {
resp := WASMResponse{ resp := WASMResponse{
Error: &WASMError{ Error: &WASMError{
Message: fmt.Sprintf("panic recovered: %v", r), Message: fmt.Sprintf("panic recovered: %v\n%s", r, debug.Stack()),
Code: 500, Code: 500,
}, },
} }

View file

@ -3,17 +3,30 @@
package d2wasm package d2wasm
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt"
"strings" "strings"
"syscall/js" "syscall/js"
"oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2compiler" "oss.terrastruct.com/d2/d2compiler"
"oss.terrastruct.com/d2/d2format" "oss.terrastruct.com/d2/d2format"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
"oss.terrastruct.com/d2/d2layouts/d2elklayout"
"oss.terrastruct.com/d2/d2lib"
"oss.terrastruct.com/d2/d2lsp" "oss.terrastruct.com/d2/d2lsp"
"oss.terrastruct.com/d2/d2oracle" "oss.terrastruct.com/d2/d2oracle"
"oss.terrastruct.com/d2/d2parser" "oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/d2renderers/d2fonts"
"oss.terrastruct.com/d2/d2renderers/d2svg"
"oss.terrastruct.com/d2/lib/log"
"oss.terrastruct.com/d2/lib/memfs"
"oss.terrastruct.com/d2/lib/textmeasure"
"oss.terrastruct.com/d2/lib/urlenc"
"oss.terrastruct.com/d2/lib/version" "oss.terrastruct.com/d2/lib/version"
"oss.terrastruct.com/util-go/go2"
) )
func GetParentID(args []js.Value) (interface{}, error) { func GetParentID(args []js.Value) (interface{}, error) {
@ -96,13 +109,62 @@ func GetRefRanges(args []js.Value) (interface{}, error) {
func Compile(args []js.Value) (interface{}, error) { func Compile(args []js.Value) (interface{}, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, &WASMError{Message: "missing script argument", Code: 400} return nil, &WASMError{Message: "missing JSON argument", Code: 400}
}
var input CompileRequest
if err := json.Unmarshal([]byte(args[0].String()), &input); err != nil {
return nil, &WASMError{Message: "invalid JSON input", Code: 400}
} }
script := args[0].String() if input.FS == nil {
g, _, err := d2compiler.Compile("", strings.NewReader(script), &d2compiler.CompileOptions{ return nil, &WASMError{Message: "missing 'fs' field in input JSON", Code: 400}
UTF16Pos: true, }
})
if _, ok := input.FS["index"]; !ok {
return nil, &WASMError{Message: "missing 'index' file in input fs", Code: 400}
}
fs, err := memfs.New(input.FS)
if err != nil {
return nil, &WASMError{Message: fmt.Sprintf("invalid fs input: %s", err.Error()), Code: 400}
}
ruler, err := textmeasure.NewRuler()
if err != nil {
return nil, &WASMError{Message: fmt.Sprintf("text ruler cannot be initialized: %s", err.Error()), Code: 500}
}
ctx := log.WithDefault(context.Background())
layoutFunc := d2dagrelayout.DefaultLayout
if input.Opts != nil && input.Opts.Layout != nil {
switch *input.Opts.Layout {
case "dagre":
layoutFunc = d2dagrelayout.DefaultLayout
case "elk":
layoutFunc = d2elklayout.DefaultLayout
default:
return nil, &WASMError{Message: fmt.Sprintf("layout option '%s' not recognized", *input.Opts.Layout), Code: 400}
}
}
layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
return layoutFunc, nil
}
renderOpts := &d2svg.RenderOpts{}
var fontFamily *d2fonts.FontFamily
if input.Opts != nil && input.Opts.Sketch != nil {
fontFamily = go2.Pointer(d2fonts.HandDrawn)
renderOpts.Sketch = input.Opts.Sketch
}
if input.Opts != nil && input.Opts.ThemeID != nil {
renderOpts.ThemeID = input.Opts.ThemeID
}
diagram, g, err := d2lib.Compile(ctx, input.FS["index"], &d2lib.CompileOptions{
UTF16Pos: true,
FS: fs,
Ruler: ruler,
LayoutResolver: layoutResolver,
FontFamily: fontFamily,
}, renderOpts)
if err != nil { if err != nil {
if pe, ok := err.(*d2parser.ParseError); ok { if pe, ok := err.(*d2parser.ParseError); ok {
return nil, &WASMError{Message: pe.Error(), Code: 400} return nil, &WASMError{Message: pe.Error(), Code: 400}
@ -110,12 +172,41 @@ func Compile(args []js.Value) (interface{}, error) {
return nil, &WASMError{Message: err.Error(), Code: 500} return nil, &WASMError{Message: err.Error(), Code: 500}
} }
newScript := d2format.Format(g.AST) input.FS["index"] = d2format.Format(g.AST)
if script != newScript {
return map[string]string{"result": newScript}, nil return CompileResponse{
FS: input.FS,
Diagram: *diagram,
Graph: *g,
}, nil
}
func Render(args []js.Value) (interface{}, error) {
if len(args) < 1 {
return nil, &WASMError{Message: "missing JSON argument", Code: 400}
}
var input RenderRequest
if err := json.Unmarshal([]byte(args[0].String()), &input); err != nil {
return nil, &WASMError{Message: "invalid JSON input", Code: 400}
} }
return nil, nil if input.Diagram == nil {
return nil, &WASMError{Message: "missing 'diagram' field in input JSON", Code: 400}
}
renderOpts := &d2svg.RenderOpts{}
if input.Opts != nil && input.Opts.Sketch != nil {
renderOpts.Sketch = input.Opts.Sketch
}
if input.Opts != nil && input.Opts.ThemeID != nil {
renderOpts.ThemeID = input.Opts.ThemeID
}
out, err := d2svg.Render(input.Diagram, renderOpts)
if err != nil {
return nil, &WASMError{Message: fmt.Sprintf("render failed: %s", err.Error()), Code: 500}
}
return out, nil
} }
func GetBoardAtPosition(args []js.Value) (interface{}, error) { func GetBoardAtPosition(args []js.Value) (interface{}, error) {
@ -144,7 +235,13 @@ func Encode(args []js.Value) (interface{}, error) {
} }
script := args[0].String() script := args[0].String()
return map[string]string{"result": script}, nil encoded, err := urlenc.Encode(script)
// should never happen
if err != nil {
return nil, &WASMError{Message: err.Error(), Code: 500}
}
return map[string]string{"result": encoded}, nil
} }
func Decode(args []js.Value) (interface{}, error) { func Decode(args []js.Value) (interface{}, error) {
@ -153,6 +250,10 @@ func Decode(args []js.Value) (interface{}, error) {
} }
script := args[0].String() script := args[0].String()
script, err := urlenc.Decode(script)
if err != nil {
return nil, &WASMError{Message: err.Error(), Code: 500}
}
return map[string]string{"result": script}, nil return map[string]string{"result": script}, nil
} }
@ -189,7 +290,3 @@ func GetCompletions(args []js.Value) (interface{}, error) {
Items: items, Items: items,
}, nil }, nil
} }
type CompletionResponse struct {
Items []map[string]interface{} `json:"items"`
}

View file

@ -2,7 +2,11 @@
package d2wasm package d2wasm
import "oss.terrastruct.com/d2/d2ast" import (
"oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2target"
)
type WASMResponse struct { type WASMResponse struct {
Data interface{} `json:"data,omitempty"` Data interface{} `json:"data,omitempty"`
@ -26,3 +30,29 @@ type RefRangesResponse struct {
type BoardPositionResponse struct { type BoardPositionResponse struct {
BoardPath []string `json:"boardPath"` BoardPath []string `json:"boardPath"`
} }
type CompileRequest struct {
FS map[string]string `json:"fs"`
Opts *RenderOptions `json:"options"`
}
type RenderOptions struct {
Layout *string `json:"layout"`
Sketch *bool `json:"sketch"`
ThemeID *int64 `json:"themeID"`
}
type CompileResponse struct {
FS map[string]string `json:"fs"`
Diagram d2target.Diagram `json:"diagram"`
Graph d2graph.Graph `json:"graph"`
}
type CompletionResponse struct {
Items []map[string]interface{} `json:"items"`
}
type RenderRequest struct {
Diagram *d2target.Diagram `json:"diagram"`
Opts *RenderOptions `json:"options"`
}

View file

@ -16,6 +16,7 @@ func main() {
api.Register("getObjOrder", d2wasm.GetObjOrder) api.Register("getObjOrder", d2wasm.GetObjOrder)
api.Register("getRefRanges", d2wasm.GetRefRanges) api.Register("getRefRanges", d2wasm.GetRefRanges)
api.Register("compile", d2wasm.Compile) api.Register("compile", d2wasm.Compile)
api.Register("render", d2wasm.Render)
api.Register("getBoardAtPosition", d2wasm.GetBoardAtPosition) api.Register("getBoardAtPosition", d2wasm.GetBoardAtPosition)
api.Register("encode", d2wasm.Encode) api.Register("encode", d2wasm.Encode)
api.Register("decode", d2wasm.Decode) api.Register("decode", d2wasm.Decode)

View file

@ -1,5 +1,3 @@
//go:build !wasm
package d2latex package d2latex
import ( import (

View file

@ -1,11 +0,0 @@
//go:build wasm
package d2latex
func Render(s string) (_ string, err error) {
return "", nil
}
func Measure(s string) (width, height int, err error) {
return
}

View file

@ -1867,6 +1867,8 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
} }
darkThemeID = opts.DarkThemeID darkThemeID = opts.DarkThemeID
scale = opts.Scale scale = opts.Scale
} else {
opts = &RenderOpts{}
} }
buf := &bytes.Buffer{} buf := &bytes.Buffer{}

View file

@ -1,5 +1,3 @@
//go:build !wasm
package textmeasure package textmeasure
import ( import (

View file

@ -1,17 +0,0 @@
//go:build wasm
package textmeasure
import "oss.terrastruct.com/d2/d2renderers/d2fonts"
func MeasureMarkdown(mdText string, ruler *Ruler, fontFamily *d2fonts.FontFamily, fontSize int) (width, height int, err error) {
return 0, 0, nil
}
func RenderMarkdown(m string) (string, error) {
return "", nil
}
func ReplaceSubstitutionsMarkdown(mdText string, variables map[string]string) string {
return mdText
}

View file

@ -1,5 +1,3 @@
//go:build !wasm
package textmeasure package textmeasure
import ( import (