From 9fc5a5f4ce6f276aa823adf293ad00801823e3b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Thu, 13 Apr 2023 18:50:39 -0300 Subject: [PATCH] wip gif export --- d2cli/export.go | 22 +++++++++++++++------- d2cli/export_test.go | 2 +- d2cli/main.go | 27 +++++++++++++++++++-------- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/d2cli/export.go b/d2cli/export.go index 8cd2f1601..a1e84db6f 100644 --- a/d2cli/export.go +++ b/d2cli/export.go @@ -1,10 +1,18 @@ package d2cli -import "path/filepath" +import ( + "path/filepath" +) type exportExtension string -var KNOWN_EXTENSIONS = []string{".svg", ".png", ".pptx", ".pdf"} +const GIF = ".gif" +const PNG = ".png" +const PPTX = ".pptx" +const PDF = ".pdf" +const SVG = ".svg" + +var KNOWN_EXTENSIONS = []string{SVG, PNG, PDF, PPTX, GIF} func getExportExtension(outputPath string) exportExtension { ext := filepath.Ext(outputPath) @@ -14,17 +22,17 @@ func getExportExtension(outputPath string) exportExtension { } } // default is svg - return exportExtension(".svg") + return exportExtension(SVG) } func (ex exportExtension) supportsAnimation() bool { - return ex == ".svg" + return ex == SVG || ex == GIF } -func (ex exportExtension) requiresPngRenderer() bool { - return ex == ".png" || ex == ".pdf" || ex == ".pptx" +func (ex exportExtension) requiresPNGRenderer() bool { + return ex == PNG || ex == PDF || ex == PPTX || ex == GIF } func (ex exportExtension) supportsDarkTheme() bool { - return ex == ".svg" + return ex == SVG } diff --git a/d2cli/export_test.go b/d2cli/export_test.go index 8518b1850..c3c6e0434 100644 --- a/d2cli/export_test.go +++ b/d2cli/export_test.go @@ -67,7 +67,7 @@ func TestOutputFormat(t *testing.T) { assert.Equal(t, tc.extension, extension) assert.Equal(t, tc.supportsAnimation, extension.supportsAnimation()) assert.Equal(t, tc.supportsDarkTheme, extension.supportsDarkTheme()) - assert.Equal(t, tc.requiresPngRender, extension.requiresPngRenderer()) + assert.Equal(t, tc.requiresPngRender, extension.requiresPNGRenderer()) }) } } diff --git a/d2cli/main.go b/d2cli/main.go index 1ceaf7371..f0c5e7ea0 100644 --- a/d2cli/main.go +++ b/d2cli/main.go @@ -38,6 +38,7 @@ import ( "oss.terrastruct.com/d2/lib/pptx" "oss.terrastruct.com/d2/lib/textmeasure" "oss.terrastruct.com/d2/lib/version" + "oss.terrastruct.com/d2/lib/xgif" "cdr.dev/slog" "cdr.dev/slog/sloggers/sloghuman" @@ -190,9 +191,10 @@ func Run(ctx context.Context, ms *xmain.State) (err error) { if outputPath != "-" { outputPath = ms.AbsPath(outputPath) if *animateIntervalFlag > 0 && !outputFormat.supportsAnimation() { - return xmain.UsageErrorf("-animate-interval can only be used when exporting to SVG.\nYou provided: %s", filepath.Ext(outputPath)) + return xmain.UsageErrorf("-animate-interval can only be used when exporting to SVG or GIF.\nYou provided: %s", filepath.Ext(outputPath)) + } else if *animateIntervalFlag <= 0 && outputFormat == GIF { + return xmain.UsageErrorf("-animate-interval must be greater than 0 for GIF outputs.\nYou provided: %d", *animateIntervalFlag) } - } match := d2themescatalog.Find(*themeFlag) @@ -242,7 +244,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) { } } var pw png.Playwright - if outputFormat.requiresPngRenderer() { + if outputFormat.requiresPNGRenderer() { pw, err = png.InitPlaywright() if err != nil { return err @@ -351,8 +353,9 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende return nil, false, err } - switch filepath.Ext(outputPath) { - case ".pdf": + ext := getExportExtension(outputPath) + switch ext { + case PDF: pageMap := buildBoardIDToIndex(diagram, nil, nil) pdf, err := renderPDF(ctx, ms, plugin, renderOpts, outputPath, page, ruler, diagram, nil, nil, pageMap) if err != nil { @@ -361,7 +364,7 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende dur := time.Since(start) ms.Log.Success.Printf("successfully compiled %s to %s in %s", ms.HumanPath(inputPath), ms.HumanPath(outputPath), dur) return pdf, true, nil - case ".pptx": + case PPTX: var username string if user, err := user.Current(); err == nil { username = user.Username @@ -398,11 +401,19 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende if err != nil { return nil, false, err } + // TODO: test GIF with 1 frame var out []byte if len(boards) > 0 { out = boards[0] if animateInterval > 0 { - out, err = d2animate.Wrap(diagram, boards, renderOpts, int(animateInterval)) + if ext == GIF { + tl, br := diagram.NestedBoundingBox() + width := png.SCALE*(br.X-tl.X) + renderOpts.Pad*2 + height := png.SCALE*(br.Y-tl.Y) + renderOpts.Pad*2 + out, err = xgif.AnimatePNGs(boards, width, height, int(animateInterval)) + } else { + out, err = d2animate.Wrap(diagram, boards, renderOpts, int(animateInterval)) + } if err != nil { return nil, false, err } @@ -599,7 +610,7 @@ 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 := filepath.Ext(outputPath) == ".png" + toPNG := getExportExtension(outputPath).requiresPNGRenderer() svg, err := d2svg.Render(diagram, &d2svg.RenderOpts{ Pad: opts.Pad, Sketch: opts.Sketch,