Skip to content

Commit c09d29f

Browse files
authored
Merge pull request #2343 from x-delfino/wasm-options
d2js: Additional render options
2 parents ba17acf + cd4116f commit c09d29f

File tree

11 files changed

+703
-85
lines changed

11 files changed

+703
-85
lines changed

ci/release/changelogs/next.md

+12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@
44

55
#### Improvements 🧹
66

7+
- d2js: Support `d2-config`. Support additional options: [#2343](https://github.com/terrastruct/d2/pull/2343)
8+
- `themeID`
9+
- `darkThemeID`
10+
- `center`
11+
- `pad`
12+
- `scale`
13+
- `forceAppendix`
14+
- `target`
15+
- `animateInterval`
16+
- `salt`
17+
- `noXMLTag`
18+
719
#### Bugfixes ⛑️
820

921
- Compiler:

d2js/d2wasm/functions.go

+194-26
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ import (
1919
"oss.terrastruct.com/d2/d2lsp"
2020
"oss.terrastruct.com/d2/d2oracle"
2121
"oss.terrastruct.com/d2/d2parser"
22+
"oss.terrastruct.com/d2/d2renderers/d2animate"
2223
"oss.terrastruct.com/d2/d2renderers/d2fonts"
2324
"oss.terrastruct.com/d2/d2renderers/d2svg"
25+
"oss.terrastruct.com/d2/d2renderers/d2svg/appendix"
26+
"oss.terrastruct.com/d2/d2target"
2427
"oss.terrastruct.com/d2/lib/log"
2528
"oss.terrastruct.com/d2/lib/memfs"
2629
"oss.terrastruct.com/d2/lib/textmeasure"
@@ -170,47 +173,61 @@ func Compile(args []js.Value) (interface{}, error) {
170173
return nil, &WASMError{Message: "missing 'index' file in input fs", Code: 400}
171174
}
172175

173-
fs, err := memfs.New(input.FS)
176+
compileOpts := &d2lib.CompileOptions{
177+
UTF16Pos: true,
178+
}
179+
180+
compileOpts.LayoutResolver = func(engine string) (d2graph.LayoutGraph, error) {
181+
switch engine {
182+
case "dagre":
183+
return d2dagrelayout.DefaultLayout, nil
184+
case "elk":
185+
return d2elklayout.DefaultLayout, nil
186+
default:
187+
return nil, &WASMError{Message: fmt.Sprintf("layout option '%s' not recognized", engine), Code: 400}
188+
}
189+
}
190+
191+
var err error
192+
compileOpts.FS, err = memfs.New(input.FS)
174193
if err != nil {
175194
return nil, &WASMError{Message: fmt.Sprintf("invalid fs input: %s", err.Error()), Code: 400}
176195
}
177196

178-
ruler, err := textmeasure.NewRuler()
197+
compileOpts.Ruler, err = textmeasure.NewRuler()
179198
if err != nil {
180199
return nil, &WASMError{Message: fmt.Sprintf("text ruler cannot be initialized: %s", err.Error()), Code: 500}
181200
}
182-
ctx := log.WithDefault(context.Background())
183-
layoutFunc := d2dagrelayout.DefaultLayout
201+
184202
if input.Opts != nil && input.Opts.Layout != nil {
185-
switch *input.Opts.Layout {
186-
case "dagre":
187-
layoutFunc = d2dagrelayout.DefaultLayout
188-
case "elk":
189-
layoutFunc = d2elklayout.DefaultLayout
190-
default:
191-
return nil, &WASMError{Message: fmt.Sprintf("layout option '%s' not recognized", *input.Opts.Layout), Code: 400}
192-
}
193-
}
194-
layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
195-
return layoutFunc, nil
203+
compileOpts.Layout = input.Opts.Layout
196204
}
197205

198206
renderOpts := &d2svg.RenderOpts{}
199-
var fontFamily *d2fonts.FontFamily
200-
if input.Opts != nil && input.Opts.Sketch != nil && *input.Opts.Sketch {
201-
fontFamily = go2.Pointer(d2fonts.HandDrawn)
207+
if input.Opts != nil && input.Opts.Sketch != nil {
202208
renderOpts.Sketch = input.Opts.Sketch
209+
if *input.Opts.Sketch {
210+
compileOpts.FontFamily = go2.Pointer(d2fonts.HandDrawn)
211+
}
212+
}
213+
if input.Opts != nil && input.Opts.Pad != nil {
214+
renderOpts.Pad = input.Opts.Pad
215+
}
216+
if input.Opts != nil && input.Opts.Center != nil {
217+
renderOpts.Center = input.Opts.Center
203218
}
204219
if input.Opts != nil && input.Opts.ThemeID != nil {
205220
renderOpts.ThemeID = input.Opts.ThemeID
206221
}
207-
diagram, g, err := d2lib.Compile(ctx, input.FS["index"], &d2lib.CompileOptions{
208-
UTF16Pos: true,
209-
FS: fs,
210-
Ruler: ruler,
211-
LayoutResolver: layoutResolver,
212-
FontFamily: fontFamily,
213-
}, renderOpts)
222+
if input.Opts != nil && input.Opts.DarkThemeID != nil {
223+
renderOpts.DarkThemeID = input.Opts.DarkThemeID
224+
}
225+
if input.Opts != nil && input.Opts.Scale != nil {
226+
renderOpts.Scale = input.Opts.Scale
227+
}
228+
229+
ctx := log.WithDefault(context.Background())
230+
diagram, g, err := d2lib.Compile(ctx, input.FS["index"], compileOpts, renderOpts)
214231
if err != nil {
215232
if pe, ok := err.(*d2parser.ParseError); ok {
216233
errs, _ := json.Marshal(pe.Errors)
@@ -225,6 +242,19 @@ func Compile(args []js.Value) (interface{}, error) {
225242
FS: input.FS,
226243
Diagram: *diagram,
227244
Graph: *g,
245+
RenderOptions: RenderOptions{
246+
ThemeID: renderOpts.ThemeID,
247+
DarkThemeID: renderOpts.DarkThemeID,
248+
Sketch: renderOpts.Sketch,
249+
Pad: renderOpts.Pad,
250+
Center: renderOpts.Center,
251+
Scale: renderOpts.Scale,
252+
ForceAppendix: input.Opts.ForceAppendix,
253+
Target: input.Opts.Target,
254+
AnimateInterval: input.Opts.AnimateInterval,
255+
Salt: input.Opts.Salt,
256+
NoXMLTag: input.Opts.NoXMLTag,
257+
},
228258
}, nil
229259
}
230260

@@ -241,21 +271,159 @@ func Render(args []js.Value) (interface{}, error) {
241271
return nil, &WASMError{Message: "missing 'diagram' field in input JSON", Code: 400}
242272
}
243273

274+
animateInterval := 0
275+
if input.Opts != nil && input.Opts.AnimateInterval != nil && *input.Opts.AnimateInterval > 0 {
276+
animateInterval = int(*input.Opts.AnimateInterval)
277+
}
278+
279+
var boardPath []string
280+
noChildren := true
281+
282+
if input.Opts.Target != nil {
283+
switch *input.Opts.Target {
284+
case "*":
285+
noChildren = false
286+
case "":
287+
default:
288+
target := *input.Opts.Target
289+
if strings.HasSuffix(target, ".*") {
290+
target = target[:len(target)-2]
291+
noChildren = false
292+
}
293+
key, err := d2parser.ParseKey(target)
294+
if err != nil {
295+
return nil, &WASMError{Message: fmt.Sprintf("target '%s' not recognized", target), Code: 400}
296+
}
297+
boardPath = key.StringIDA()
298+
}
299+
if !noChildren && animateInterval <= 0 {
300+
return nil, &WASMError{Message: fmt.Sprintf("target '%s' only supported for animated SVGs", *input.Opts.Target), Code: 500}
301+
}
302+
}
303+
304+
diagram := input.Diagram.GetBoard(boardPath)
305+
if diagram == nil {
306+
return nil, &WASMError{Message: fmt.Sprintf("render target '%s' not found", strings.Join(boardPath, ".")), Code: 400}
307+
}
308+
if noChildren {
309+
diagram.Layers = nil
310+
diagram.Scenarios = nil
311+
diagram.Steps = nil
312+
}
313+
244314
renderOpts := &d2svg.RenderOpts{}
315+
316+
if input.Opts != nil && input.Opts.Salt != nil {
317+
renderOpts.Salt = input.Opts.Salt
318+
}
319+
320+
if animateInterval > 0 {
321+
masterID, err := diagram.HashID(renderOpts.Salt)
322+
if err != nil {
323+
return nil, &WASMError{Message: fmt.Sprintf("cannot process animate interval: %s", err.Error()), Code: 500}
324+
}
325+
renderOpts.MasterID = masterID
326+
}
327+
328+
ruler, err := textmeasure.NewRuler()
329+
if err != nil {
330+
return nil, &WASMError{Message: fmt.Sprintf("text ruler cannot be initialized: %s", err.Error()), Code: 500}
331+
}
332+
245333
if input.Opts != nil && input.Opts.Sketch != nil {
246334
renderOpts.Sketch = input.Opts.Sketch
247335
}
336+
if input.Opts != nil && input.Opts.Pad != nil {
337+
renderOpts.Pad = input.Opts.Pad
338+
}
339+
if input.Opts != nil && input.Opts.Center != nil {
340+
renderOpts.Center = input.Opts.Center
341+
}
248342
if input.Opts != nil && input.Opts.ThemeID != nil {
249343
renderOpts.ThemeID = input.Opts.ThemeID
250344
}
251-
out, err := d2svg.Render(input.Diagram, renderOpts)
345+
if input.Opts != nil && input.Opts.DarkThemeID != nil {
346+
renderOpts.DarkThemeID = input.Opts.DarkThemeID
347+
}
348+
if input.Opts != nil && input.Opts.Scale != nil {
349+
renderOpts.Scale = input.Opts.Scale
350+
}
351+
if input.Opts != nil && input.Opts.NoXMLTag != nil {
352+
renderOpts.NoXMLTag = input.Opts.NoXMLTag
353+
}
354+
355+
forceAppendix := input.Opts != nil && input.Opts.ForceAppendix != nil && *input.Opts.ForceAppendix
356+
357+
var boards [][]byte
358+
if noChildren {
359+
var board []byte
360+
board, err = renderSingleBoard(renderOpts, forceAppendix, ruler, diagram)
361+
boards = [][]byte{board}
362+
} else {
363+
boards, err = renderBoards(renderOpts, forceAppendix, ruler, diagram)
364+
}
252365
if err != nil {
253366
return nil, &WASMError{Message: fmt.Sprintf("render failed: %s", err.Error()), Code: 500}
254367
}
255368

369+
var out []byte
370+
if len(boards) > 0 {
371+
out = boards[0]
372+
if animateInterval > 0 {
373+
out, err = d2animate.Wrap(diagram, boards, *renderOpts, animateInterval)
374+
if err != nil {
375+
return nil, &WASMError{Message: fmt.Sprintf("animation failed: %s", err.Error()), Code: 500}
376+
}
377+
}
378+
}
379+
return out, nil
380+
}
381+
382+
func renderSingleBoard(opts *d2svg.RenderOpts, forceAppendix bool, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([]byte, error) {
383+
out, err := d2svg.Render(diagram, opts)
384+
if err != nil {
385+
return nil, &WASMError{Message: fmt.Sprintf("render failed: %s", err.Error()), Code: 500}
386+
}
387+
if forceAppendix {
388+
out = appendix.Append(diagram, opts, ruler, out)
389+
}
256390
return out, nil
257391
}
258392

393+
func renderBoards(opts *d2svg.RenderOpts, forceAppendix bool, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([][]byte, error) {
394+
var boards [][]byte
395+
for _, dl := range diagram.Layers {
396+
childrenBoards, err := renderBoards(opts, forceAppendix, ruler, dl)
397+
if err != nil {
398+
return nil, err
399+
}
400+
boards = append(boards, childrenBoards...)
401+
}
402+
for _, dl := range diagram.Scenarios {
403+
childrenBoards, err := renderBoards(opts, forceAppendix, ruler, dl)
404+
if err != nil {
405+
return nil, err
406+
}
407+
boards = append(boards, childrenBoards...)
408+
}
409+
for _, dl := range diagram.Steps {
410+
childrenBoards, err := renderBoards(opts, forceAppendix, ruler, dl)
411+
if err != nil {
412+
return nil, err
413+
}
414+
boards = append(boards, childrenBoards...)
415+
}
416+
417+
if !diagram.IsFolderOnly {
418+
out, err := renderSingleBoard(opts, forceAppendix, ruler, diagram)
419+
if err != nil {
420+
return boards, err
421+
}
422+
boards = append([][]byte{out}, boards...)
423+
}
424+
return boards, nil
425+
}
426+
259427
func GetBoardAtPosition(args []js.Value) (interface{}, error) {
260428
if len(args) < 3 {
261429
return nil, &WASMError{Message: "missing required arguments", Code: 400}

d2js/d2wasm/types.go

+21-7
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,33 @@ type BoardPositionResponse struct {
3333

3434
type CompileRequest struct {
3535
FS map[string]string `json:"fs"`
36-
Opts *RenderOptions `json:"options"`
36+
Opts *CompileOptions `json:"options"`
3737
}
3838

3939
type RenderOptions struct {
40-
Layout *string `json:"layout"`
41-
Sketch *bool `json:"sketch"`
42-
ThemeID *int64 `json:"themeID"`
40+
Pad *int64 `json:"pad"`
41+
Sketch *bool `json:"sketch"`
42+
Center *bool `json:"center"`
43+
ThemeID *int64 `json:"themeID"`
44+
DarkThemeID *int64 `json:"darkThemeID"`
45+
Scale *float64 `json:"scale"`
46+
ForceAppendix *bool `json:"forceAppendix"`
47+
Target *string `json:"target"`
48+
AnimateInterval *int64 `json:"animateInterval"`
49+
Salt *string `json:"salt"`
50+
NoXMLTag *bool `json:"noXMLTag"`
51+
}
52+
53+
type CompileOptions struct {
54+
RenderOptions
55+
Layout *string `json:"layout"`
4356
}
4457

4558
type CompileResponse struct {
46-
FS map[string]string `json:"fs"`
47-
Diagram d2target.Diagram `json:"diagram"`
48-
Graph d2graph.Graph `json:"graph"`
59+
FS map[string]string `json:"fs"`
60+
Diagram d2target.Diagram `json:"diagram"`
61+
Graph d2graph.Graph `json:"graph"`
62+
RenderOptions RenderOptions `json:"renderOptions"`
4963
}
5064

5165
type CompletionResponse struct {

0 commit comments

Comments
 (0)