From 98e61995e4748fadfb14eab3c729d5fe30a66d32 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 17 Mar 2023 22:29:51 -0700 Subject: [PATCH] center flag --- ci/release/changelogs/next.md | 2 + ci/release/template/man/d2.1 | 3 + d2cli/main.go | 35 ++++--- d2cli/watch.go | 3 +- d2renderers/d2svg/d2svg.go | 8 +- e2etests-cli/main_test.go | 10 ++ .../testdata/TestCLI_E2E/center.exp.svg | 95 ++++++++++++++++++ .../TestCLI_E2E/internal_linked_pdf.exp.pdf | Bin 81564 -> 81564 bytes 8 files changed, 140 insertions(+), 16 deletions(-) create mode 100644 e2etests-cli/testdata/TestCLI_E2E/center.exp.svg diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 67e612848..eb695339e 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -1,5 +1,7 @@ #### Features ๐Ÿš€ +- `--center` flag centers the SVG in the containing viewbox. [#1056](https://github.com/terrastruct/d2/pull/1056) + #### Improvements ๐Ÿงน - `elk` layout containers no longer overlap the label with children. [#1055](https://github.com/terrastruct/d2/pull/1055) diff --git a/ci/release/template/man/d2.1 b/ci/release/template/man/d2.1 index ed1b37017..cc012ef7e 100644 --- a/ci/release/template/man/d2.1 +++ b/ci/release/template/man/d2.1 @@ -77,6 +77,9 @@ making style maps in D2 light/dark mode specific. See .It Fl s , -sketch Ar false Renders the diagram to look like it was sketched by hand .Ns . +.It Fl -center Ar flag +Center the SVG in the containing viewbox, such as your browser screen +.Ns . .It Fl -pad Ar 100 Pixels padded around the rendered diagram .Ns . diff --git a/d2cli/main.go b/d2cli/main.go index 5c92efb5e..4763b8cff 100644 --- a/d2cli/main.go +++ b/d2cli/main.go @@ -83,6 +83,10 @@ func Run(ctx context.Context, ms *xmain.State) (err error) { if err != nil { return err } + centerFlag, err := ms.Opts.Bool("D2_CENTER", "center", "c", false, "center the SVG in the containing viewbox, such as your browser screen") + if err != nil { + return err + } ps, err := d2plugin.ListPlugins(ctx) if err != nil { @@ -229,6 +233,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) { w, err := newWatcher(ctx, ms, watcherOpts{ layoutPlugin: plugin, sketch: *sketchFlag, + center: *centerFlag, themeID: *themeFlag, darkThemeID: darkThemeFlag, pad: *padFlag, @@ -249,7 +254,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) { ctx, cancel := context.WithTimeout(ctx, time.Minute*2) defer cancel() - _, written, err := compile(ctx, ms, plugin, *sketchFlag, *padFlag, *themeFlag, darkThemeFlag, inputPath, outputPath, *bundleFlag, *forceAppendixFlag, pw.Page) + _, written, err := compile(ctx, ms, plugin, *sketchFlag, *centerFlag, *padFlag, *themeFlag, darkThemeFlag, inputPath, outputPath, *bundleFlag, *forceAppendixFlag, pw.Page) if err != nil { if written { return fmt.Errorf("failed to fully compile (partial render written): %w", err) @@ -259,7 +264,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) { return nil } -func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, sketch bool, pad, themeID int64, darkThemeID *int64, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page) (_ []byte, written bool, _ error) { +func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, sketch, center bool, pad, themeID int64, darkThemeID *int64, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page) (_ []byte, written bool, _ error) { start := time.Now() input, err := ms.ReadPath(inputPath) if err != nil { @@ -298,10 +303,10 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, sketc var svg []byte if filepath.Ext(outputPath) == ".pdf" { pageMap := pdf.BuildPDFPageMap(diagram, nil, nil) - svg, err = renderPDF(ctx, ms, plugin, sketch, pad, themeID, outputPath, page, ruler, diagram, nil, nil, pageMap) + svg, err = renderPDF(ctx, ms, plugin, sketch, center, pad, themeID, outputPath, page, ruler, diagram, nil, nil, pageMap) } else { compileDur := time.Since(start) - svg, err = render(ctx, ms, compileDur, plugin, sketch, pad, themeID, darkThemeID, inputPath, outputPath, bundle, forceAppendix, page, ruler, diagram) + svg, err = render(ctx, ms, compileDur, plugin, sketch, center, pad, themeID, darkThemeID, inputPath, outputPath, bundle, forceAppendix, page, ruler, diagram) } if err != nil { return svg, false, err @@ -315,7 +320,7 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, sketc return svg, true, nil } -func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plugin d2plugin.Plugin, sketch bool, pad int64, themeID int64, darkThemeID *int64, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([]byte, error) { +func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plugin d2plugin.Plugin, sketch, center bool, pad int64, themeID int64, darkThemeID *int64, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([]byte, error) { if diagram.Name != "" { ext := filepath.Ext(outputPath) outputPath = strings.TrimSuffix(outputPath, ext) @@ -359,19 +364,19 @@ func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plug } for _, dl := range diagram.Layers { - _, err := render(ctx, ms, compileDur, plugin, sketch, pad, themeID, darkThemeID, inputPath, layersOutputPath, bundle, forceAppendix, page, ruler, dl) + _, err := render(ctx, ms, compileDur, plugin, sketch, center, pad, themeID, darkThemeID, inputPath, layersOutputPath, bundle, forceAppendix, page, ruler, dl) if err != nil { return nil, err } } for _, dl := range diagram.Scenarios { - _, err := render(ctx, ms, compileDur, plugin, sketch, pad, themeID, darkThemeID, inputPath, scenariosOutputPath, bundle, forceAppendix, page, ruler, dl) + _, err := render(ctx, ms, compileDur, plugin, sketch, center, pad, themeID, darkThemeID, inputPath, scenariosOutputPath, bundle, forceAppendix, page, ruler, dl) if err != nil { return nil, err } } for _, dl := range diagram.Steps { - _, err := render(ctx, ms, compileDur, plugin, sketch, pad, themeID, darkThemeID, inputPath, stepsOutputPath, bundle, forceAppendix, page, ruler, dl) + _, err := render(ctx, ms, compileDur, plugin, sketch, center, pad, themeID, darkThemeID, inputPath, stepsOutputPath, bundle, forceAppendix, page, ruler, dl) if err != nil { return nil, err } @@ -379,7 +384,7 @@ func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plug if !diagram.IsFolderOnly { start := time.Now() - svg, err := _render(ctx, ms, plugin, sketch, pad, themeID, darkThemeID, boardOutputPath, bundle, forceAppendix, page, ruler, diagram) + svg, err := _render(ctx, ms, plugin, sketch, center, pad, themeID, darkThemeID, boardOutputPath, bundle, forceAppendix, page, ruler, diagram) if err != nil { return svg, err } @@ -391,11 +396,12 @@ func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plug return nil, nil } -func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, sketch bool, pad int64, themeID int64, darkThemeID *int64, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([]byte, error) { +func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, sketch, center bool, pad int64, themeID int64, darkThemeID *int64, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([]byte, error) { toPNG := filepath.Ext(outputPath) == ".png" svg, err := d2svg.Render(diagram, &d2svg.RenderOpts{ Pad: int(pad), Sketch: sketch, + Center: center, ThemeID: themeID, DarkThemeID: darkThemeID, SetDimensions: toPNG, @@ -457,7 +463,7 @@ func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, sketc return svg, nil } -func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, sketch bool, pad, themeID int64, outputPath string, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram, pdf *pdflib.GoFPDF, boardPath []string, pageMap map[string]int) (svg []byte, err error) { +func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, sketch, center bool, pad, themeID int64, outputPath string, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram, pdf *pdflib.GoFPDF, boardPath []string, pageMap map[string]int) (svg []byte, err error) { var isRoot bool if pdf == nil { pdf = pdflib.Init() @@ -485,6 +491,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, ske svg, err = d2svg.Render(diagram, &d2svg.RenderOpts{ Pad: int(pad), Sketch: sketch, + Center: center, SetDimensions: true, }) if err != nil { @@ -525,19 +532,19 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, ske } for _, dl := range diagram.Layers { - _, err := renderPDF(ctx, ms, plugin, sketch, pad, themeID, "", page, ruler, dl, pdf, currBoardPath, pageMap) + _, err := renderPDF(ctx, ms, plugin, sketch, center, pad, themeID, "", page, ruler, dl, pdf, currBoardPath, pageMap) if err != nil { return nil, err } } for _, dl := range diagram.Scenarios { - _, err := renderPDF(ctx, ms, plugin, sketch, pad, themeID, "", page, ruler, dl, pdf, currBoardPath, pageMap) + _, err := renderPDF(ctx, ms, plugin, sketch, center, pad, themeID, "", page, ruler, dl, pdf, currBoardPath, pageMap) if err != nil { return nil, err } } for _, dl := range diagram.Steps { - _, err := renderPDF(ctx, ms, plugin, sketch, pad, themeID, "", page, ruler, dl, pdf, currBoardPath, pageMap) + _, err := renderPDF(ctx, ms, plugin, sketch, center, pad, themeID, "", page, ruler, dl, pdf, currBoardPath, pageMap) if err != nil { return nil, err } diff --git a/d2cli/watch.go b/d2cli/watch.go index 9e3e80010..f9cfb9e81 100644 --- a/d2cli/watch.go +++ b/d2cli/watch.go @@ -44,6 +44,7 @@ type watcherOpts struct { darkThemeID *int64 pad int64 sketch bool + center bool host string port string inputPath string @@ -359,7 +360,7 @@ func (w *watcher) compileLoop(ctx context.Context) error { w.pw = newPW } - svg, _, err := compile(ctx, w.ms, w.layoutPlugin, w.sketch, w.pad, w.themeID, w.darkThemeID, w.inputPath, w.outputPath, w.bundle, w.forceAppendix, w.pw.Page) + svg, _, err := compile(ctx, w.ms, w.layoutPlugin, w.sketch, w.center, w.pad, w.themeID, w.darkThemeID, w.inputPath, w.outputPath, w.bundle, w.forceAppendix, w.pw.Page) errs := "" if err != nil { if len(svg) > 0 { diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index b745bcf95..36292decc 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -71,6 +71,7 @@ var grain string type RenderOpts struct { Pad int Sketch bool + Center bool ThemeID int64 DarkThemeID *int64 // disables the fit to screen behavior and ensures the exported svg has the exact dimensions @@ -1828,9 +1829,14 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { dimensions = fmt.Sprintf(` width="%d" height="%d"`, w, h) } - fitToScreenWrapper := fmt.Sprintf(``, + alignment := "xMinYMin" + if opts.Center { + alignment = "xMidYMid" + } + fitToScreenWrapper := fmt.Sprintf(``, `xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"`, version.Version, + alignment, w, h, dimensions, ) diff --git a/e2etests-cli/main_test.go b/e2etests-cli/main_test.go index 05595d4d4..2fdc0896d 100644 --- a/e2etests-cli/main_test.go +++ b/e2etests-cli/main_test.go @@ -45,6 +45,16 @@ func TestCLI_E2E(t *testing.T) { testdataIgnoreDiff(t, ".png", png) }, }, + { + name: "center", + run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) { + writeFile(t, dir, "hello-world.d2", `x -> y`) + err := runTestMain(t, ctx, dir, env, "--center=true", "hello-world.d2") + assert.Success(t, err) + svg := readFile(t, dir, "hello-world.svg") + assert.Testdata(t, ".svg", svg) + }, + }, { name: "hello_world_png_sketch", skipCI: true, diff --git a/e2etests-cli/testdata/TestCLI_E2E/center.exp.svg b/e2etests-cli/testdata/TestCLI_E2E/center.exp.svg new file mode 100644 index 000000000..222c52ea4 --- /dev/null +++ b/e2etests-cli/testdata/TestCLI_E2E/center.exp.svg @@ -0,0 +1,95 @@ +xy + + + diff --git a/e2etests-cli/testdata/TestCLI_E2E/internal_linked_pdf.exp.pdf b/e2etests-cli/testdata/TestCLI_E2E/internal_linked_pdf.exp.pdf index 324598cbd2f674eca6d47c66ffc985ad17ec330d..57cc95f9c7a35081ca63c6247a1dfcc4ade7bb6b 100644 GIT binary patch delta 564 zcmZXOJxjw-6o$FQq?n?`q$S;&eu-7Vdv9)TOsgVVbaM0uSU<2(6~Tf|-9$I>4A)8* zyObh=kfF2C$w6>)skk~TZUt)u5-?g~IJg$Rh2N@^!{|@mskMwEN;)6BJCZ2b=LmW#a7SPEzNWYG< delta 559 zcmZXOze@sP7{__VJMjd=^H2$*Q9jBsI2|nh;14xI~e|Eso(cyoA$Bf_{MB1mOM5gn%qRTNSX*7r`U&SB$7&O5e(6&0(* zP^MY$zKGJOsmS89pmmWF2;HO%jB^Y1aWh%tMpQA~Zz ruzZ4=sr$M*Hhj{$O8?8S!6r(sVXWyhZzxfI9Dfyc-BeAM-9qP|azu`I