From 8563cf83c646a8dae0a2fafcb43a35c6ebb090cf Mon Sep 17 00:00:00 2001 From: Gavin Nishizawa Date: Thu, 15 Jun 2023 15:53:23 -0700 Subject: [PATCH 1/4] use scale flag for no-fit and customizing zoom --- ci/release/template/man/d2.1 | 3 ++ d2cli/main.go | 76 +++++++++++++++++++++++++++--------- d2cli/static/watch.js | 14 ++++--- d2cli/watch.go | 10 +++-- d2renderers/d2svg/d2svg.go | 15 ++++--- 5 files changed, 84 insertions(+), 34 deletions(-) diff --git a/ci/release/template/man/d2.1 b/ci/release/template/man/d2.1 index 81e1557cb5..2a72fc4d5d 100644 --- a/ci/release/template/man/d2.1 +++ b/ci/release/template/man/d2.1 @@ -80,6 +80,9 @@ Renders the diagram to look like it was sketched by hand .It Fl -center Ar flag Center the SVG in the containing viewbox, such as your browser screen .Ns . +.It Fl -scale Ar fit +Set a fixed render scale (e.g. 0.5 to halve rendered size). If none provided, SVG will fit to screen +.Ns . .It Fl -font-regular Path to .ttf file to use for the regular font. If none provided, Source Sans Pro Regular is used .Ns . diff --git a/d2cli/main.go b/d2cli/main.go index 5fcf94c5dc..90f550b2ce 100644 --- a/d2cli/main.go +++ b/d2cli/main.go @@ -107,6 +107,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) { if err != nil { return err } + scaleFlag := ms.Opts.String("SCALE", "scale", "", "fit", "set a fixed render scale (e.g. 0.5 to halve rendered size). If none provided, SVG will fit to screen.") fontRegularFlag := ms.Opts.String("D2_FONT_REGULAR", "font-regular", "", "", "path to .ttf file to use for the regular font. If none provided, Source Sans Pro Regular is used.") fontItalicFlag := ms.Opts.String("D2_FONT_ITALIC", "font-italic", "", "", "path to .ttf file to use for the italic font. If none provided, Source Sans Pro Regular-Italic is used.") @@ -232,6 +233,14 @@ func Run(ctx context.Context, ms *xmain.State) (err error) { } ms.Log.Debug.Printf("using dark theme %s (ID: %d)", match.Name, *darkThemeFlag) } + var scale *float64 + if scaleFlag != nil && *scaleFlag != "fit" { + f, err := strconv.ParseFloat(*scaleFlag, 64) + if err != nil || f <= 0. { + return xmain.UsageErrorf("-scale must a positive floating point number or \"fit\".") + } + scale = &f + } plugin, err := d2plugin.FindPlugin(ctx, ps, *layoutFlag) if err != nil { @@ -282,6 +291,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) { Center: *centerFlag, ThemeID: *themeFlag, DarkThemeID: darkThemeFlag, + Scale: scale, } if *watchFlag { @@ -665,14 +675,20 @@ func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plug func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([]byte, error) { toPNG := getExportExtension(outputPath) == PNG + var scale *float64 + if opts.Scale != nil { + scale = opts.Scale + } else if toPNG { + scale = go2.Pointer(1.) + } svg, err := d2svg.Render(diagram, &d2svg.RenderOpts{ - Pad: opts.Pad, - Sketch: opts.Sketch, - Center: opts.Center, - ThemeID: opts.ThemeID, - DarkThemeID: opts.DarkThemeID, - MasterID: opts.MasterID, - SetDimensions: toPNG, + Pad: opts.Pad, + Sketch: opts.Sketch, + Center: opts.Center, + ThemeID: opts.ThemeID, + DarkThemeID: opts.DarkThemeID, + MasterID: opts.MasterID, + Scale: scale, }) if err != nil { return nil, err @@ -746,11 +762,18 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt // make the bg fill within the png transparent so that the pdf bg fill is the only bg color present diagram.Root.Fill = "transparent" + var scale *float64 + if opts.Scale != nil { + scale = opts.Scale + } else { + scale = go2.Pointer(1.) + } + svg, err = d2svg.Render(diagram, &d2svg.RenderOpts{ - Pad: opts.Pad, - Sketch: opts.Sketch, - Center: opts.Center, - SetDimensions: true, + Pad: opts.Pad, + Sketch: opts.Sketch, + Center: opts.Center, + Scale: scale, }) if err != nil { return nil, err @@ -837,12 +860,20 @@ func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Present // make the bg fill within the png transparent so that the pdf bg fill is the only bg color present diagram.Root.Fill = "transparent" + var scale *float64 + if opts.Scale != nil { + scale = opts.Scale + } else { + scale = go2.Pointer(1.) + } + var err error + svg, err = d2svg.Render(diagram, &d2svg.RenderOpts{ - Pad: opts.Pad, - Sketch: opts.Sketch, - Center: opts.Center, - SetDimensions: true, + Pad: opts.Pad, + Sketch: opts.Sketch, + Center: opts.Center, + Scale: scale, }) if err != nil { return nil, err @@ -1076,11 +1107,18 @@ func buildBoardIDToIndex(diagram *d2target.Diagram, dictionary map[string]int, p func renderPNGsForGIF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, ruler *textmeasure.Ruler, page playwright.Page, diagram *d2target.Diagram) (svg []byte, pngs [][]byte, err error) { if !diagram.IsFolderOnly { + + var scale *float64 + if opts.Scale != nil { + scale = opts.Scale + } else { + scale = go2.Pointer(1.) + } svg, err = d2svg.Render(diagram, &d2svg.RenderOpts{ - Pad: opts.Pad, - Sketch: opts.Sketch, - Center: opts.Center, - SetDimensions: true, + Pad: opts.Pad, + Sketch: opts.Sketch, + Center: opts.Center, + Scale: scale, }) if err != nil { return nil, nil, err diff --git a/d2cli/static/watch.js b/d2cli/static/watch.js index 3cb3a75e18..a954039715 100644 --- a/d2cli/static/watch.js +++ b/d2cli/static/watch.js @@ -35,12 +35,16 @@ function init(reconnectDelay) { let width = parseInt(svgEl.getAttribute("width"), 10); let height = parseInt(svgEl.getAttribute("height"), 10); if (isInit) { - if (width > height) { - if (width > window.innerWidth) { - ratio = window.innerWidth / width; + if (msg.scale) { + ratio = msg.scale; + } else { + if (width > height) { + if (width > window.innerWidth) { + ratio = window.innerWidth / width; + } + } else if (height > window.innerHeight) { + ratio = window.innerHeight / height; } - } else if (height > window.innerHeight) { - ratio = window.innerHeight / height; } // Scale svg fit to zoom isInit = false; diff --git a/d2cli/watch.go b/d2cli/watch.go index 047e415d54..bb006eb17a 100644 --- a/d2cli/watch.go +++ b/d2cli/watch.go @@ -83,8 +83,9 @@ type watcher struct { } type compileResult struct { - SVG string `json:"svg"` - Err string `json:"err"` + SVG string `json:"svg"` + Scale *float64 `json:"scale,omitEmpty"` + Err string `json:"err"` } func newWatcher(ctx context.Context, ms *xmain.State, opts watcherOpts) (*watcher, error) { @@ -372,8 +373,9 @@ func (w *watcher) compileLoop(ctx context.Context) error { w.ms.Log.Error.Print(errs) } w.broadcast(&compileResult{ - SVG: string(svg), - Err: errs, + SVG: string(svg), + Scale: w.renderOpts.Scale, + Err: errs, }) if firstCompile { diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index 11996eb54d..5531d8716b 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -75,8 +75,8 @@ type RenderOpts struct { ThemeID int64 DarkThemeID *int64 Font string - // disables the fit to screen behavior and ensures the exported svg has the exact dimensions - SetDimensions bool + // the svg will be scaled by this factor, if unset the svg will fit to screen + Scale *float64 // MasterID is passed when the diagram should use something other than its own hash for unique targeting // Currently, that's when multi-boards are collapsed @@ -1648,7 +1648,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { pad := DEFAULT_PADDING themeID := DEFAULT_THEME darkThemeID := DEFAULT_DARK_THEME - setDimensions := false + var scale *float64 if opts != nil { pad = opts.Pad if opts.Sketch { @@ -1660,7 +1660,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { } themeID = opts.ThemeID darkThemeID = opts.DarkThemeID - setDimensions = opts.SetDimensions + scale = opts.Scale } buf := &bytes.Buffer{} @@ -1851,8 +1851,11 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { } var dimensions string - if setDimensions { - dimensions = fmt.Sprintf(` width="%d" height="%d"`, w, h) + if scale != nil { + dimensions = fmt.Sprintf(` width="%d" height="%d"`, + int(math.Ceil((*scale)*float64(w))), + int(math.Ceil((*scale)*float64(h))), + ) } alignment := "xMinYMin" From 5ea81a5b90bf857543507454d34aba0ba3022512 Mon Sep 17 00:00:00 2001 From: Gavin Nishizawa Date: Thu, 15 Jun 2023 17:03:34 -0700 Subject: [PATCH 2/4] changelog --- ci/release/changelogs/next.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 823e8dd45a..5ecb3a68f2 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -1,6 +1,7 @@ #### Features ๐Ÿš€ - Configure timeout value with D2_TIMEOUT env var. [#1392](https://github.com/terrastruct/d2/pull/1392) +- Scale renders and disable fit to screen with `--scale` flag. [#1413](https://github.com/terrastruct/d2/pull/1413) #### Improvements ๐Ÿงน From 65059546eb77b9f59dd21dd72d96d88162621365 Mon Sep 17 00:00:00 2001 From: Gavin Nishizawa Date: Mon, 26 Jun 2023 13:26:55 -0700 Subject: [PATCH 3/4] use float for flag --- ci/release/template/man/d2.1 | 2 +- d2cli/main.go | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/ci/release/template/man/d2.1 b/ci/release/template/man/d2.1 index 2a72fc4d5d..1bb889994f 100644 --- a/ci/release/template/man/d2.1 +++ b/ci/release/template/man/d2.1 @@ -80,7 +80,7 @@ Renders the diagram to look like it was sketched by hand .It Fl -center Ar flag Center the SVG in the containing viewbox, such as your browser screen .Ns . -.It Fl -scale Ar fit +.It Fl -scale Ar -1 Set a fixed render scale (e.g. 0.5 to halve rendered size). If none provided, SVG will fit to screen .Ns . .It Fl -font-regular diff --git a/d2cli/main.go b/d2cli/main.go index 90f550b2ce..d36e03c4ae 100644 --- a/d2cli/main.go +++ b/d2cli/main.go @@ -107,7 +107,10 @@ func Run(ctx context.Context, ms *xmain.State) (err error) { if err != nil { return err } - scaleFlag := ms.Opts.String("SCALE", "scale", "", "fit", "set a fixed render scale (e.g. 0.5 to halve rendered size). If none provided, SVG will fit to screen.") + scaleFlag, err := ms.Opts.Float64("SCALE", "scale", "", -1, "set a fixed render scale (e.g. 0.5 to halve rendered size). If none provided, SVG will fit to screen.") + if err != nil { + return err + } fontRegularFlag := ms.Opts.String("D2_FONT_REGULAR", "font-regular", "", "", "path to .ttf file to use for the regular font. If none provided, Source Sans Pro Regular is used.") fontItalicFlag := ms.Opts.String("D2_FONT_ITALIC", "font-italic", "", "", "path to .ttf file to use for the italic font. If none provided, Source Sans Pro Regular-Italic is used.") @@ -234,12 +237,8 @@ func Run(ctx context.Context, ms *xmain.State) (err error) { ms.Log.Debug.Printf("using dark theme %s (ID: %d)", match.Name, *darkThemeFlag) } var scale *float64 - if scaleFlag != nil && *scaleFlag != "fit" { - f, err := strconv.ParseFloat(*scaleFlag, 64) - if err != nil || f <= 0. { - return xmain.UsageErrorf("-scale must a positive floating point number or \"fit\".") - } - scale = &f + if scaleFlag != nil && *scaleFlag > 0. { + scale = scaleFlag } plugin, err := d2plugin.FindPlugin(ctx, ps, *layoutFlag) From b18c7e28b5e62b164b7e564ea8799035b7fd0578 Mon Sep 17 00:00:00 2001 From: Gavin Nishizawa Date: Mon, 26 Jun 2023 13:45:15 -0700 Subject: [PATCH 4/4] copy --- ci/release/template/man/d2.1 | 2 +- d2cli/main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/release/template/man/d2.1 b/ci/release/template/man/d2.1 index 1bb889994f..d1ebc59395 100644 --- a/ci/release/template/man/d2.1 +++ b/ci/release/template/man/d2.1 @@ -81,7 +81,7 @@ Renders the diagram to look like it was sketched by hand Center the SVG in the containing viewbox, such as your browser screen .Ns . .It Fl -scale Ar -1 -Set a fixed render scale (e.g. 0.5 to halve rendered size). If none provided, SVG will fit to screen +Scale the output. E.g., 0.5 to halve the default size. Default -1 means that SVG's will fit to screen and all others will use their default render size. Setting to 1 turns off SVG fitting to screen .Ns . .It Fl -font-regular Path to .ttf file to use for the regular font. If none provided, Source Sans Pro Regular is used diff --git a/d2cli/main.go b/d2cli/main.go index d36e03c4ae..3d4fa60542 100644 --- a/d2cli/main.go +++ b/d2cli/main.go @@ -107,7 +107,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) { if err != nil { return err } - scaleFlag, err := ms.Opts.Float64("SCALE", "scale", "", -1, "set a fixed render scale (e.g. 0.5 to halve rendered size). If none provided, SVG will fit to screen.") + scaleFlag, err := ms.Opts.Float64("SCALE", "scale", "", -1, "scale the output. E.g., 0.5 to halve the default size. Default -1 means that SVG's will fit to screen and all others will use their default render size. Setting to 1 turns off SVG fitting to screen.") if err != nil { return err }