From c3a4c9aa1cd5b9d1d75504a82e29e3a4d3f2c9ed Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Wed, 16 Nov 2022 17:41:53 -0800 Subject: [PATCH 01/30] Use Playwright to export images to png --- cmd/d2/main.go | 38 ++++++++++++--- cmd/d2/watch.go | 19 +++++++- go.mod | 5 +- go.sum | 9 ++++ lib/png/generate_png.js | 23 +++++++++ lib/png/png.go | 103 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 187 insertions(+), 10 deletions(-) create mode 100644 lib/png/generate_png.js create mode 100644 lib/png/png.go diff --git a/cmd/d2/main.go b/cmd/d2/main.go index a960a3d23..277b61008 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -10,8 +10,7 @@ import ( "strings" "time" - _ "embed" - + "github.com/playwright-community/playwright-go" "github.com/spf13/pflag" "oss.terrastruct.com/d2" @@ -20,6 +19,7 @@ import ( "oss.terrastruct.com/d2/d2renderers/textmeasure" "oss.terrastruct.com/d2/d2themes" "oss.terrastruct.com/d2/d2themes/d2themescatalog" + "oss.terrastruct.com/d2/lib/png" "oss.terrastruct.com/d2/lib/version" "oss.terrastruct.com/d2/lib/xmain" ) @@ -113,12 +113,28 @@ func run(ctx context.Context, ms *xmain.State) (err error) { } ms.Log.Debug.Printf("using layout plugin %s (%s)", envD2Layout, pluginLocation) + var pw *playwright.Playwright + var browser playwright.Browser + if filepath.Ext(outputPath) == ".png" { + pw, browser, err = png.InitPlaywright() + if err != nil { + return err + } + } + defer func() error { + err = png.Cleanup(pw, browser) + if err != nil { + return err + } + return nil + }() + if *watchFlag { if inputPath == "-" { return xmain.UsageErrorf("-w[atch] cannot be combined with reading input from stdin") } ms.Env.Setenv("LOG_TIMESTAMPS", "1") - w, err := newWatcher(ctx, ms, plugin, inputPath, outputPath) + w, err := newWatcher(ctx, ms, plugin, inputPath, outputPath, pw, browser) if err != nil { return err } @@ -132,15 +148,16 @@ func run(ctx context.Context, ms *xmain.State) (err error) { _ = 343 } - _, err = compile(ctx, ms, plugin, inputPath, outputPath) + _, err = compile(ctx, ms, plugin, inputPath, outputPath, browser) if err != nil { return err } + ms.Log.Success.Printf("successfully compiled %v to %v", inputPath, outputPath) return nil } -func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, inputPath, outputPath string) ([]byte, error) { +func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, inputPath, outputPath string, browser playwright.Browser) ([]byte, error) { input, err := ms.ReadPath(inputPath) if err != nil { return nil, err @@ -165,12 +182,19 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, input if err != nil { return nil, err } - svg, err = plugin.PostProcess(ctx, svg) + outputImage, err := plugin.PostProcess(ctx, svg) if err != nil { return nil, err } - err = ms.WritePath(outputPath, svg) + if filepath.Ext(outputPath) == ".png" { + outputImage, err = png.ExportPNG(browser, svg) + if err != nil { + return nil, err + } + } + + err = ms.WritePath(outputPath, outputImage) if err != nil { return nil, err } diff --git a/cmd/d2/watch.go b/cmd/d2/watch.go index f9570da66..d1f31056b 100644 --- a/cmd/d2/watch.go +++ b/cmd/d2/watch.go @@ -16,6 +16,7 @@ import ( "time" "github.com/fsnotify/fsnotify" + "github.com/playwright-community/playwright-go" "nhooyr.io/websocket" "nhooyr.io/websocket/wsjson" @@ -61,6 +62,9 @@ type watcher struct { resMu sync.Mutex res *compileResult + + browser playwright.Browser + pw *playwright.Playwright } type compileResult struct { @@ -68,7 +72,7 @@ type compileResult struct { SVG string `json:"svg"` } -func newWatcher(ctx context.Context, ms *xmain.State, layoutPlugin d2plugin.Plugin, inputPath, outputPath string) (*watcher, error) { +func newWatcher(ctx context.Context, ms *xmain.State, layoutPlugin d2plugin.Plugin, inputPath, outputPath string, pw *playwright.Playwright, browser playwright.Browser) (*watcher, error) { ctx, cancel := context.WithCancel(ctx) w := &watcher{ @@ -83,6 +87,8 @@ func newWatcher(ctx context.Context, ms *xmain.State, layoutPlugin d2plugin.Plug compileCh: make(chan struct{}, 1), wsclients: make(map[*wsclient]struct{}), + browser: browser, + pw: pw, } err := w.init() if err != nil { @@ -143,6 +149,7 @@ func (w *watcher) run() error { func (w *watcher) close() { w.wsclientsMu.Lock() + if w.closing { w.wsclientsMu.Unlock() return @@ -325,7 +332,15 @@ func (w *watcher) compileLoop(ctx context.Context) error { recompiledPrefix = "re" } - b, err := compile(ctx, w.ms, w.layoutPlugin, w.inputPath, w.outputPath) + if !w.browser.IsConnected() { + newBrowser, err := w.pw.Chromium.Launch() + if err != nil { + return err + } + w.browser = newBrowser + } + + b, err := compile(ctx, w.ms, w.layoutPlugin, w.inputPath, w.outputPath, w.browser) if err != nil { err = fmt.Errorf("failed to %scompile: %w", recompiledPrefix, err) w.ms.Log.Error.Print(err) diff --git a/go.mod b/go.mod index 72c8feb60..ad2069c4d 100644 --- a/go.mod +++ b/go.mod @@ -33,19 +33,21 @@ require ( require ( cloud.google.com/go/compute v1.7.0 // indirect github.com/andybalholm/cascadia v1.3.1 // indirect + github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect github.com/fatih/color v1.13.0 // indirect github.com/gin-gonic/gin v1.7.7 // indirect github.com/go-playground/validator/v10 v10.10.0 // indirect + github.com/go-stack/stack v1.8.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/gorilla/websocket v1.4.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/playwright-community/playwright-go v0.2000.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/ugorji/go/codec v1.2.6 // indirect @@ -57,6 +59,7 @@ require ( google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index c9976a706..532eafc6f 100644 --- a/go.sum +++ b/go.sum @@ -88,6 +88,8 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -130,6 +132,8 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= @@ -223,6 +227,7 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -266,6 +271,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/playwright-community/playwright-go v0.2000.1 h1:2JViSHpJQ/UL/PO1Gg6gXV5IcXAAsoBJ3KG9L3wKXto= +github.com/playwright-community/playwright-go v0.2000.1/go.mod h1:1y9cM9b9dVHnuRWzED1KLM7FtbwTJC8ibDjI6MNqewU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -751,6 +758,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= +gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/lib/png/generate_png.js b/lib/png/generate_png.js new file mode 100644 index 000000000..99eccc61e --- /dev/null +++ b/lib/png/generate_png.js @@ -0,0 +1,23 @@ +async (imgString) => { + const exportMaxWidth = 4000; + const tempImg = new Image(); + const loadImage = () => { + return new Promise((resolve, reject) => { + tempImg.onload = (event) => resolve(event.currentTarget); + tempImg.onerror = () => { + reject("error loading string as an image"); + }; + tempImg.src = imgString; + }); + }; + const img = await loadImage(); + const canvas = document.createElement("canvas"); + canvas.width = Math.min(img.width, exportMaxWidth); + canvas.height = (canvas.width * img.height) / img.width; + const ctx = canvas.getContext("2d"); + if (!ctx) { + return ""; + } + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); + return canvas.toDataURL("image/png"); +} diff --git a/lib/png/png.go b/lib/png/png.go new file mode 100644 index 000000000..a32d74179 --- /dev/null +++ b/lib/png/png.go @@ -0,0 +1,103 @@ +package png + +import ( + "bytes" + "encoding/base64" + "errors" + "fmt" + "os" + "os/exec" + "strings" + + _ "embed" + + "github.com/playwright-community/playwright-go" +) + +func InitPlaywright() (*playwright.Playwright, playwright.Browser, error) { + // check if playwright driver/browsers are installed and up to date + // https://github.com/playwright-community/playwright-go/blob/8e8f670b5fa7ba5365ae4bfc123fea4aac359763/run.go#L64. + driver, err := playwright.NewDriver(&playwright.RunOptions{}) + if err != nil { + return nil, nil, err + } + if _, err := os.Stat(driver.DriverBinaryLocation); errors.Is(err, os.ErrNotExist) { + err = playwright.Install() + if err != nil { + return nil, nil, err + } + } else if err == nil { + cmd := exec.Command(driver.DriverBinaryLocation, "--version") + output, err := cmd.Output() + if err != nil || !bytes.Contains(output, []byte(driver.Version)) { + err = playwright.Install() + if err != nil { + return nil, nil, err + } + } + } + + pw, err := playwright.Run() + if err != nil { + return nil, nil, err + } + browser, err := pw.Chromium.Launch() + if err != nil { + return nil, nil, err + } + return pw, browser, nil +} + +//go:embed generate_png.js +var genPNGScript string + +func ExportPNG(browser playwright.Browser, svg []byte) (outputImage []byte, err error) { + var page playwright.Page + defer func() error { + err = page.Close() + if err != nil { + return err + } + return nil + }() + + if browser == nil { + return nil, fmt.Errorf("browser is not initialized for png export") + } + page, err = browser.NewPage() + if err != nil { + return nil, err + } + encodedSVG := base64.StdEncoding.EncodeToString(svg) + pngInterface, err := page.Evaluate(genPNGScript, "data:image/svg+xml;charset=utf-8;base64,"+encodedSVG) + if err != nil { + return nil, err + } + + pngString := fmt.Sprintf("%v", pngInterface) + pngPrefix := "data:image/png;base64," + if !strings.HasPrefix(pngString, pngPrefix) { + return nil, fmt.Errorf("playwright export generated invalid png") + } + splicedPNGString := pngString[len(pngPrefix):] + outputImage, err = base64.StdEncoding.DecodeString(splicedPNGString) + if err != nil { + return nil, err + } + + return outputImage, nil +} + +func Cleanup(pw *playwright.Playwright, browser playwright.Browser) (err error) { + if browser != nil { + if err = browser.Close(); err != nil { + return err + } + } + if pw != nil { + if err = pw.Stop(); err != nil { + return err + } + } + return nil +} From e5e34362fe9972c931a2bdfe68ce2f29d17b1d1f Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Wed, 16 Nov 2022 18:44:43 -0800 Subject: [PATCH 02/30] PR Comments --- cmd/d2/main.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/d2/main.go b/cmd/d2/main.go index 277b61008..6331db81c 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -120,14 +120,14 @@ func run(ctx context.Context, ms *xmain.State) (err error) { if err != nil { return err } + defer func() error { + err = png.Cleanup(pw, browser) + if err != nil { + return err + } + return nil + }() } - defer func() error { - err = png.Cleanup(pw, browser) - if err != nil { - return err - } - return nil - }() if *watchFlag { if inputPath == "-" { From 6ef42465659b9eaea1140deb138a46ab64c16e61 Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 11:25:07 -0800 Subject: [PATCH 03/30] create Playwright struct, use BrowserContext --- cmd/d2/main.go | 15 +++--- cmd/d2/watch.go | 17 ++++--- lib/png/png.go | 120 +++++++++++++++++++++++++++++++----------------- 3 files changed, 92 insertions(+), 60 deletions(-) diff --git a/cmd/d2/main.go b/cmd/d2/main.go index 6331db81c..8f86b270d 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -113,15 +113,14 @@ func run(ctx context.Context, ms *xmain.State) (err error) { } ms.Log.Debug.Printf("using layout plugin %s (%s)", envD2Layout, pluginLocation) - var pw *playwright.Playwright - var browser playwright.Browser + var pw png.Playwright if filepath.Ext(outputPath) == ".png" { - pw, browser, err = png.InitPlaywright() + pw, err = png.InitPlaywright() if err != nil { return err } defer func() error { - err = png.Cleanup(pw, browser) + err = pw.Cleanup(*watchFlag) if err != nil { return err } @@ -134,7 +133,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) { return xmain.UsageErrorf("-w[atch] cannot be combined with reading input from stdin") } ms.Env.Setenv("LOG_TIMESTAMPS", "1") - w, err := newWatcher(ctx, ms, plugin, inputPath, outputPath, pw, browser) + w, err := newWatcher(ctx, ms, plugin, inputPath, outputPath, pw) if err != nil { return err } @@ -148,7 +147,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) { _ = 343 } - _, err = compile(ctx, ms, plugin, inputPath, outputPath, browser) + _, err = compile(ctx, ms, plugin, inputPath, outputPath, pw.Page) if err != nil { return err } @@ -157,7 +156,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) { return nil } -func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, inputPath, outputPath string, browser playwright.Browser) ([]byte, error) { +func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, inputPath, outputPath string, page playwright.Page) ([]byte, error) { input, err := ms.ReadPath(inputPath) if err != nil { return nil, err @@ -188,7 +187,7 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, input } if filepath.Ext(outputPath) == ".png" { - outputImage, err = png.ExportPNG(browser, svg) + outputImage, err = png.ExportPNG(ms, page, svg) if err != nil { return nil, err } diff --git a/cmd/d2/watch.go b/cmd/d2/watch.go index d1f31056b..0930caf5b 100644 --- a/cmd/d2/watch.go +++ b/cmd/d2/watch.go @@ -16,11 +16,11 @@ import ( "time" "github.com/fsnotify/fsnotify" - "github.com/playwright-community/playwright-go" "nhooyr.io/websocket" "nhooyr.io/websocket/wsjson" "oss.terrastruct.com/d2/d2plugin" + "oss.terrastruct.com/d2/lib/png" "oss.terrastruct.com/d2/lib/xbrowser" "oss.terrastruct.com/d2/lib/xhttp" "oss.terrastruct.com/d2/lib/xmain" @@ -63,8 +63,7 @@ type watcher struct { resMu sync.Mutex res *compileResult - browser playwright.Browser - pw *playwright.Playwright + pw png.Playwright } type compileResult struct { @@ -72,7 +71,7 @@ type compileResult struct { SVG string `json:"svg"` } -func newWatcher(ctx context.Context, ms *xmain.State, layoutPlugin d2plugin.Plugin, inputPath, outputPath string, pw *playwright.Playwright, browser playwright.Browser) (*watcher, error) { +func newWatcher(ctx context.Context, ms *xmain.State, layoutPlugin d2plugin.Plugin, inputPath, outputPath string, pw png.Playwright) (*watcher, error) { ctx, cancel := context.WithCancel(ctx) w := &watcher{ @@ -87,7 +86,6 @@ func newWatcher(ctx context.Context, ms *xmain.State, layoutPlugin d2plugin.Plug compileCh: make(chan struct{}, 1), wsclients: make(map[*wsclient]struct{}), - browser: browser, pw: pw, } err := w.init() @@ -332,15 +330,16 @@ func (w *watcher) compileLoop(ctx context.Context) error { recompiledPrefix = "re" } - if !w.browser.IsConnected() { - newBrowser, err := w.pw.Chromium.Launch() + if filepath.Ext(w.outputPath) == ".png" && !w.pw.Browser.IsConnected() { + newPW, err := w.pw.RestartBrowser() if err != nil { + w.ms.Log.Error.Printf("failed to refresh Playwright browser") return err } - w.browser = newBrowser + w.pw = newPW } - b, err := compile(ctx, w.ms, w.layoutPlugin, w.inputPath, w.outputPath, w.browser) + b, err := compile(ctx, w.ms, w.layoutPlugin, w.inputPath, w.outputPath, w.pw.Page) if err != nil { err = fmt.Errorf("failed to %scompile: %w", recompiledPrefix, err) w.ms.Log.Error.Print(err) diff --git a/lib/png/png.go b/lib/png/png.go index a32d74179..d3c0f4a74 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -12,19 +12,69 @@ import ( _ "embed" "github.com/playwright-community/playwright-go" + "oss.terrastruct.com/d2/lib/xmain" ) -func InitPlaywright() (*playwright.Playwright, playwright.Browser, error) { +type Playwright struct { + PW *playwright.Playwright + Browser playwright.Browser + BrowserContext playwright.BrowserContext + Page playwright.Page +} + +func (pw *Playwright) RestartBrowser() (newPW Playwright, err error) { + if err = pw.BrowserContext.Close(); err != nil { + return Playwright{}, err + } + if err = pw.Browser.Close(); err != nil { + return Playwright{}, err + } + browser, err := pw.PW.Chromium.Launch() + if err != nil { + return Playwright{}, err + } + context, err := browser.NewContext() + if err != nil { + return Playwright{}, err + } + page, err := context.NewPage() + if err != nil { + return Playwright{}, err + } + return Playwright{ + PW: pw.PW, + Browser: browser, + BrowserContext: context, + Page: page, + }, nil +} + +func (pw *Playwright) Cleanup(isWatch bool) (err error) { + if !isWatch { + if err = pw.BrowserContext.Close(); err != nil { + return err + } + } + if err = pw.Browser.Close(); err != nil { + return err + } + if err = pw.PW.Stop(); err != nil { + return err + } + return nil +} + +func InitPlaywright() (Playwright, error) { // check if playwright driver/browsers are installed and up to date // https://github.com/playwright-community/playwright-go/blob/8e8f670b5fa7ba5365ae4bfc123fea4aac359763/run.go#L64. driver, err := playwright.NewDriver(&playwright.RunOptions{}) if err != nil { - return nil, nil, err + return Playwright{}, err } if _, err := os.Stat(driver.DriverBinaryLocation); errors.Is(err, os.ErrNotExist) { err = playwright.Install() if err != nil { - return nil, nil, err + return Playwright{}, err } } else if err == nil { cmd := exec.Command(driver.DriverBinaryLocation, "--version") @@ -32,42 +82,44 @@ func InitPlaywright() (*playwright.Playwright, playwright.Browser, error) { if err != nil || !bytes.Contains(output, []byte(driver.Version)) { err = playwright.Install() if err != nil { - return nil, nil, err + return Playwright{}, err } } } pw, err := playwright.Run() if err != nil { - return nil, nil, err + return Playwright{}, err } browser, err := pw.Chromium.Launch() if err != nil { - return nil, nil, err + return Playwright{}, err } - return pw, browser, nil + context, err := browser.NewContext() + if err != nil { + return Playwright{}, err + } + page, err := context.NewPage() + if err != nil { + return Playwright{}, err + } + return Playwright{ + PW: pw, + Browser: browser, + BrowserContext: context, + Page: page, + }, nil } //go:embed generate_png.js var genPNGScript string -func ExportPNG(browser playwright.Browser, svg []byte) (outputImage []byte, err error) { - var page playwright.Page - defer func() error { - err = page.Close() - if err != nil { - return err - } - return nil - }() +func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage []byte, err error) { + if page == nil { + ms.Log.Error.Printf("Playwright was not initialized properly for PNG export") + return nil, fmt.Errorf("Playwright page is not initialized for png export") + } - if browser == nil { - return nil, fmt.Errorf("browser is not initialized for png export") - } - page, err = browser.NewPage() - if err != nil { - return nil, err - } encodedSVG := base64.StdEncoding.EncodeToString(svg) pngInterface, err := page.Evaluate(genPNGScript, "data:image/svg+xml;charset=utf-8;base64,"+encodedSVG) if err != nil { @@ -77,27 +129,9 @@ func ExportPNG(browser playwright.Browser, svg []byte) (outputImage []byte, err pngString := fmt.Sprintf("%v", pngInterface) pngPrefix := "data:image/png;base64," if !strings.HasPrefix(pngString, pngPrefix) { + ms.Log.Error.Printf("failed to convert D2 file to PNG") return nil, fmt.Errorf("playwright export generated invalid png") } splicedPNGString := pngString[len(pngPrefix):] - outputImage, err = base64.StdEncoding.DecodeString(splicedPNGString) - if err != nil { - return nil, err - } - - return outputImage, nil -} - -func Cleanup(pw *playwright.Playwright, browser playwright.Browser) (err error) { - if browser != nil { - if err = browser.Close(); err != nil { - return err - } - } - if pw != nil { - if err = pw.Stop(); err != nil { - return err - } - } - return nil + return base64.StdEncoding.DecodeString(splicedPNGString) } From 0e70c0baa5a887a71c6d819fa29592e2a8e4de1c Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 11:34:14 -0800 Subject: [PATCH 04/30] Small tweaks --- cmd/d2/watch.go | 1 - lib/png/png.go | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/d2/watch.go b/cmd/d2/watch.go index 0930caf5b..4e25bed95 100644 --- a/cmd/d2/watch.go +++ b/cmd/d2/watch.go @@ -147,7 +147,6 @@ func (w *watcher) run() error { func (w *watcher) close() { w.wsclientsMu.Lock() - if w.closing { w.wsclientsMu.Unlock() return diff --git a/lib/png/png.go b/lib/png/png.go index d3c0f4a74..0cdc1e872 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -117,7 +117,7 @@ var genPNGScript string func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage []byte, err error) { if page == nil { ms.Log.Error.Printf("Playwright was not initialized properly for PNG export") - return nil, fmt.Errorf("Playwright page is not initialized for png export") + return nil, fmt.Errorf("Playwright page is nil") } encodedSVG := base64.StdEncoding.EncodeToString(svg) @@ -130,7 +130,7 @@ func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage [ pngPrefix := "data:image/png;base64," if !strings.HasPrefix(pngString, pngPrefix) { ms.Log.Error.Printf("failed to convert D2 file to PNG") - return nil, fmt.Errorf("playwright export generated invalid png") + return nil, fmt.Errorf("Playwright export generated invalid png") } splicedPNGString := pngString[len(pngPrefix):] return base64.StdEncoding.DecodeString(splicedPNGString) From 8dfd3a478ed1831be4c29fac493a291cf0162348 Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 11:47:35 -0800 Subject: [PATCH 05/30] Add to 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 ffd49ca9c..d12aa07e4 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -3,6 +3,7 @@ For v0.0.99 we focused on X, Y and Z. Enjoy! #### Features 💸 - Now you can easily do x, y and z #9999 +- Ability to export to PNGs #### Improvements 🔧 From 530c70445a33c6e15e9a33f769c7162be8a410ab Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 14:13:23 -0800 Subject: [PATCH 06/30] Update help, manpages, and cleanup --- ci/release/template/man/d2.1 | 6 ++++-- cmd/d2/help.go | 4 ++-- cmd/d2/main.go | 10 +++------- lib/png/png.go | 19 ++++++------------- 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/ci/release/template/man/d2.1 b/ci/release/template/man/d2.1 index 6d187ea83..918828661 100644 --- a/ci/release/template/man/d2.1 +++ b/ci/release/template/man/d2.1 @@ -9,7 +9,7 @@ .Op Fl -watch Ar false .Op Fl -theme Em 0 .Ar file.d2 -.Op Ar file.svg +.Op Ar file.svg|file.png .Nm d2 .Op Fl -watch Ar false .Op Fl -theme Em 0 @@ -22,7 +22,9 @@ compiles and renders .Ar file.d2 to -.Ar file.svg +.Ar file.svg +or +.Ar png .Ns . .Pp Pass - to have diff --git a/cmd/d2/help.go b/cmd/d2/help.go index 961c9c275..ba80bc50a 100644 --- a/cmd/d2/help.go +++ b/cmd/d2/help.go @@ -15,9 +15,9 @@ import ( func help(ms *xmain.State) { fmt.Fprintf(ms.Stdout, `Usage: - %s [--watch=false] [--theme=0] file.d2 [file.svg] + %s [--watch=false] [--theme=0] file.d2 [file.svg|file.png] -%[1]s compiles and renders file.d2 to file.svg +%[1]s compiles and renders file.d2 to file.svg or png Use - to have d2 read from stdin or write to stdout. Flags: diff --git a/cmd/d2/main.go b/cmd/d2/main.go index 8f86b270d..c09b90e46 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -34,7 +34,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) { watchFlag := ms.FlagSet.BoolP("watch", "w", false, "watch for changes to input and live reload. Use $PORT and $HOST to specify the listening address.\n$D2_PORT and $D2_HOST are also accepted and take priority. Default is localhost:0") themeFlag := ms.FlagSet.Int64P("theme", "t", 0, "set the diagram theme. For a list of available options, see https://oss.terrastruct.com/d2") - bundleFlag := ms.FlagSet.BoolP("bundle", "b", true, "bundle all assets and layers into the output svg") + bundleFlag := ms.FlagSet.BoolP("bundle", "b", true, "when outputting SVG, bundle all assets and layers into the output file") versionFlag := ms.FlagSet.BoolP("version", "v", false, "get the version") debugFlag := ms.FlagSet.BoolP("debug", "d", false, "print debug logs") err = ms.FlagSet.Parse(ms.Args) @@ -119,12 +119,8 @@ func run(ctx context.Context, ms *xmain.State) (err error) { if err != nil { return err } - defer func() error { - err = pw.Cleanup(*watchFlag) - if err != nil { - return err - } - return nil + defer func() { + err = pw.Cleanup() }() } diff --git a/lib/png/png.go b/lib/png/png.go index 0cdc1e872..997a0dcd1 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -23,9 +23,6 @@ type Playwright struct { } func (pw *Playwright) RestartBrowser() (newPW Playwright, err error) { - if err = pw.BrowserContext.Close(); err != nil { - return Playwright{}, err - } if err = pw.Browser.Close(); err != nil { return Playwright{}, err } @@ -49,12 +46,7 @@ func (pw *Playwright) RestartBrowser() (newPW Playwright, err error) { }, nil } -func (pw *Playwright) Cleanup(isWatch bool) (err error) { - if !isWatch { - if err = pw.BrowserContext.Close(); err != nil { - return err - } - } +func (pw *Playwright) Cleanup() (err error) { if err = pw.Browser.Close(); err != nil { return err } @@ -116,8 +108,7 @@ var genPNGScript string func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage []byte, err error) { if page == nil { - ms.Log.Error.Printf("Playwright was not initialized properly for PNG export") - return nil, fmt.Errorf("Playwright page is nil") + return nil, fmt.Errorf("Playwright was not initialized properly for PNG export") } encodedSVG := base64.StdEncoding.EncodeToString(svg) @@ -129,8 +120,10 @@ func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage [ pngString := fmt.Sprintf("%v", pngInterface) pngPrefix := "data:image/png;base64," if !strings.HasPrefix(pngString, pngPrefix) { - ms.Log.Error.Printf("failed to convert D2 file to PNG") - return nil, fmt.Errorf("Playwright export generated invalid png") + if len(pngString) > 20 { + pngString = pngString[0:20] + "..." + } + return nil, fmt.Errorf("invalid PNG: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", pngString) } splicedPNGString := pngString[len(pngPrefix):] return base64.StdEncoding.DecodeString(splicedPNGString) From 93ed44c9782fb187e35d50d7ba41f85939e46b5a Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 14:24:59 -0800 Subject: [PATCH 07/30] handle cleanup error correctly --- cmd/d2/main.go | 5 ++++- lib/png/png.go | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cmd/d2/main.go b/cmd/d2/main.go index c09b90e46..ae4d3f421 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -120,7 +120,10 @@ func run(ctx context.Context, ms *xmain.State) (err error) { return err } defer func() { - err = pw.Cleanup() + cleanupErr := pw.Cleanup() + if cleanupErr != nil { + ms.Log.Error.Printf("error cleaning up playwright: %v", cleanupErr.Error()) + } }() } diff --git a/lib/png/png.go b/lib/png/png.go index 997a0dcd1..6fa4542a1 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -120,8 +120,8 @@ func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage [ pngString := fmt.Sprintf("%v", pngInterface) pngPrefix := "data:image/png;base64," if !strings.HasPrefix(pngString, pngPrefix) { - if len(pngString) > 20 { - pngString = pngString[0:20] + "..." + if len(pngString) > 50 { + pngString = pngString[0:50] + "..." } return nil, fmt.Errorf("invalid PNG: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", pngString) } From dc8fdda9496bcfcc1d636473a19cc79cec7a5cb7 Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 14:32:56 -0800 Subject: [PATCH 08/30] fix copy --- ci/release/template/man/d2.1 | 6 +----- cmd/d2/help.go | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/ci/release/template/man/d2.1 b/ci/release/template/man/d2.1 index 918828661..6d548cb0f 100644 --- a/ci/release/template/man/d2.1 +++ b/ci/release/template/man/d2.1 @@ -21,11 +21,7 @@ .Nm compiles and renders .Ar file.d2 -to -.Ar file.svg -or -.Ar png -.Ns . +to svg or png. .Pp Pass - to have .Nm diff --git a/cmd/d2/help.go b/cmd/d2/help.go index ba80bc50a..69e33bb98 100644 --- a/cmd/d2/help.go +++ b/cmd/d2/help.go @@ -17,7 +17,7 @@ func help(ms *xmain.State) { fmt.Fprintf(ms.Stdout, `Usage: %s [--watch=false] [--theme=0] file.d2 [file.svg|file.png] -%[1]s compiles and renders file.d2 to file.svg or png +%[1]s compiles and renders file.d2 to svg or png. Use - to have d2 read from stdin or write to stdout. Flags: From 3fee69a556ffce0e7b8822060659dcfed2a6296d Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 14:45:50 -0800 Subject: [PATCH 09/30] use postprocessed svg --- cmd/d2/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/d2/main.go b/cmd/d2/main.go index ae4d3f421..b21467b9e 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -186,7 +186,7 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, input } if filepath.Ext(outputPath) == ".png" { - outputImage, err = png.ExportPNG(ms, page, svg) + outputImage, err = png.ExportPNG(ms, page, outputImage) if err != nil { return nil, err } From 4b631899175c024dff9c4455b285ddec7ffa69d9 Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 14:49:42 -0800 Subject: [PATCH 10/30] throw a better error on browser restart failure --- cmd/d2/watch.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/d2/watch.go b/cmd/d2/watch.go index 4e25bed95..df53d6190 100644 --- a/cmd/d2/watch.go +++ b/cmd/d2/watch.go @@ -332,8 +332,7 @@ func (w *watcher) compileLoop(ctx context.Context) error { if filepath.Ext(w.outputPath) == ".png" && !w.pw.Browser.IsConnected() { newPW, err := w.pw.RestartBrowser() if err != nil { - w.ms.Log.Error.Printf("failed to refresh Playwright browser") - return err + return fmt.Errorf("png exporter has disconnected") } w.pw = newPW } From e9d915f8a8e8391910774e85828f16db1c273221 Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 14:55:43 -0800 Subject: [PATCH 11/30] Remove context from Playwright object --- lib/png/png.go | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/png/png.go b/lib/png/png.go index 6fa4542a1..90e771c2c 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -16,10 +16,9 @@ import ( ) type Playwright struct { - PW *playwright.Playwright - Browser playwright.Browser - BrowserContext playwright.BrowserContext - Page playwright.Page + PW *playwright.Playwright + Browser playwright.Browser + Page playwright.Page } func (pw *Playwright) RestartBrowser() (newPW Playwright, err error) { @@ -39,10 +38,9 @@ func (pw *Playwright) RestartBrowser() (newPW Playwright, err error) { return Playwright{}, err } return Playwright{ - PW: pw.PW, - Browser: browser, - BrowserContext: context, - Page: page, + PW: pw.PW, + Browser: browser, + Page: page, }, nil } @@ -96,10 +94,9 @@ func InitPlaywright() (Playwright, error) { return Playwright{}, err } return Playwright{ - PW: pw, - Browser: browser, - BrowserContext: context, - Page: page, + PW: pw, + Browser: browser, + Page: page, }, nil } From c2d8d17f4668e1013757ebbb8caf768c4f936fca Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 16:41:28 -0800 Subject: [PATCH 12/30] Cleanup png.Playwright.Init --- cmd/d2/watch.go | 6 +++- lib/png/png.go | 82 ++++++++++++++++++++++--------------------------- 2 files changed, 42 insertions(+), 46 deletions(-) diff --git a/cmd/d2/watch.go b/cmd/d2/watch.go index df53d6190..89b74da27 100644 --- a/cmd/d2/watch.go +++ b/cmd/d2/watch.go @@ -332,7 +332,11 @@ func (w *watcher) compileLoop(ctx context.Context) error { if filepath.Ext(w.outputPath) == ".png" && !w.pw.Browser.IsConnected() { newPW, err := w.pw.RestartBrowser() if err != nil { - return fmt.Errorf("png exporter has disconnected") + err = fmt.Errorf("png exporter has disconnected") + w.ms.Log.Error.Print(err) + w.broadcast(&compileResult{ + Err: err.Error(), + }) } w.pw = newPW } diff --git a/lib/png/png.go b/lib/png/png.go index 90e771c2c..7fbcc67b6 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -3,7 +3,6 @@ package png import ( "bytes" "encoding/base64" - "errors" "fmt" "os" "os/exec" @@ -25,23 +24,7 @@ func (pw *Playwright) RestartBrowser() (newPW Playwright, err error) { if err = pw.Browser.Close(); err != nil { return Playwright{}, err } - browser, err := pw.PW.Chromium.Launch() - if err != nil { - return Playwright{}, err - } - context, err := browser.NewContext() - if err != nil { - return Playwright{}, err - } - page, err := context.NewPage() - if err != nil { - return Playwright{}, err - } - return Playwright{ - PW: pw.PW, - Browser: browser, - Page: page, - }, nil + return startPlaywright(pw.PW) } func (pw *Playwright) Cleanup() (err error) { @@ -54,33 +37,7 @@ func (pw *Playwright) Cleanup() (err error) { return nil } -func InitPlaywright() (Playwright, error) { - // check if playwright driver/browsers are installed and up to date - // https://github.com/playwright-community/playwright-go/blob/8e8f670b5fa7ba5365ae4bfc123fea4aac359763/run.go#L64. - driver, err := playwright.NewDriver(&playwright.RunOptions{}) - if err != nil { - return Playwright{}, err - } - if _, err := os.Stat(driver.DriverBinaryLocation); errors.Is(err, os.ErrNotExist) { - err = playwright.Install() - if err != nil { - return Playwright{}, err - } - } else if err == nil { - cmd := exec.Command(driver.DriverBinaryLocation, "--version") - output, err := cmd.Output() - if err != nil || !bytes.Contains(output, []byte(driver.Version)) { - err = playwright.Install() - if err != nil { - return Playwright{}, err - } - } - } - - pw, err := playwright.Run() - if err != nil { - return Playwright{}, err - } +func startPlaywright(pw *playwright.Playwright) (Playwright, error) { browser, err := pw.Chromium.Launch() if err != nil { return Playwright{}, err @@ -100,6 +57,41 @@ func InitPlaywright() (Playwright, error) { }, nil } +func InitPlaywright() (Playwright, error) { + // check if playwright driver/browsers are installed and up to date + // https://github.com/playwright-community/playwright-go/blob/8e8f670b5fa7ba5365ae4bfc123fea4aac359763/run.go#L64. + driver, err := playwright.NewDriver(&playwright.RunOptions{}) + if err != nil { + return Playwright{}, err + } + if _, err := os.Stat(driver.DriverBinaryLocation); os.IsNotExist(err) { + err = playwright.Install() + if err != nil { + return Playwright{}, err + } + } else if err == nil { + cmd := exec.Command(driver.DriverBinaryLocation, "--version") + output, err := cmd.Output() + if err != nil { + return Playwright{}, fmt.Errorf("could not install Playwright driver") + } + if !bytes.Contains(output, []byte(driver.Version)) { + err = playwright.Install() + if err != nil { + return Playwright{}, err + } + } + } else { + return Playwright{}, fmt.Errorf("could not install Playwright driver") + } + + pw, err := playwright.Run() + if err != nil { + return Playwright{}, err + } + return startPlaywright(pw) +} + //go:embed generate_png.js var genPNGScript string From 6a00cfb005c4874eacc2ac5625969e90542e2926 Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 16:49:07 -0800 Subject: [PATCH 13/30] Report playwright installation errors --- lib/png/png.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/png/png.go b/lib/png/png.go index 7fbcc67b6..e65ac2f5e 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -73,7 +73,7 @@ func InitPlaywright() (Playwright, error) { cmd := exec.Command(driver.DriverBinaryLocation, "--version") output, err := cmd.Output() if err != nil { - return Playwright{}, fmt.Errorf("could not install Playwright driver") + return Playwright{}, fmt.Errorf("could not install Playwright driver: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) } if !bytes.Contains(output, []byte(driver.Version)) { err = playwright.Install() @@ -82,7 +82,7 @@ func InitPlaywright() (Playwright, error) { } } } else { - return Playwright{}, fmt.Errorf("could not install Playwright driver") + return Playwright{}, fmt.Errorf("could not find Playwright binary: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) } pw, err := playwright.Run() From 3f517fd1c3e10345da7d930b81e3c84ab602cc5c Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 16:56:38 -0800 Subject: [PATCH 14/30] obfuscate Playwright from errors --- lib/png/png.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/png/png.go b/lib/png/png.go index e65ac2f5e..e1cc821f1 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -73,7 +73,7 @@ func InitPlaywright() (Playwright, error) { cmd := exec.Command(driver.DriverBinaryLocation, "--version") output, err := cmd.Output() if err != nil { - return Playwright{}, fmt.Errorf("could not install Playwright driver: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) + return Playwright{}, fmt.Errorf("error installing png exporter: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) } if !bytes.Contains(output, []byte(driver.Version)) { err = playwright.Install() @@ -82,7 +82,7 @@ func InitPlaywright() (Playwright, error) { } } } else { - return Playwright{}, fmt.Errorf("could not find Playwright binary: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) + return Playwright{}, fmt.Errorf("could not install png exporter: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) } pw, err := playwright.Run() @@ -97,7 +97,7 @@ var genPNGScript string func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage []byte, err error) { if page == nil { - return nil, fmt.Errorf("Playwright was not initialized properly for PNG export") + return nil, fmt.Errorf("png exporter was not initialized properly\nplease report this issue here: https://github.com/terrastruct/d2/issues/new") } encodedSVG := base64.StdEncoding.EncodeToString(svg) From 1658a2e0f4189f5775152866e0160d0655fd97ac Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 16:58:01 -0800 Subject: [PATCH 15/30] one more error to obfuscate --- cmd/d2/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/d2/main.go b/cmd/d2/main.go index b21467b9e..e423166bc 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -122,7 +122,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) { defer func() { cleanupErr := pw.Cleanup() if cleanupErr != nil { - ms.Log.Error.Printf("error cleaning up playwright: %v", cleanupErr.Error()) + ms.Log.Error.Printf("error cleaning up png exporter: %v", cleanupErr.Error()) } }() } From 49f0a049fef58eb909c3017cd54ec399120958b0 Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 17:22:08 -0800 Subject: [PATCH 16/30] Use xdefer to append issue creation message --- cmd/d2/main.go | 6 ++++-- cmd/d2/watch.go | 1 - lib/png/png.go | 19 ++++++++++++++----- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/cmd/d2/main.go b/cmd/d2/main.go index e423166bc..443c65acf 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -22,6 +22,7 @@ import ( "oss.terrastruct.com/d2/lib/png" "oss.terrastruct.com/d2/lib/version" "oss.terrastruct.com/d2/lib/xmain" + "oss.terrastruct.com/xdefer" ) func main() { @@ -122,7 +123,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) { defer func() { cleanupErr := pw.Cleanup() if cleanupErr != nil { - ms.Log.Error.Printf("error cleaning up png exporter: %v", cleanupErr.Error()) + ms.Log.Error.Printf(cleanupErr.Error()) } }() } @@ -155,7 +156,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) { return nil } -func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, inputPath, outputPath string, page playwright.Page) ([]byte, error) { +func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, inputPath, outputPath string, page playwright.Page) (_ []byte, err error) { input, err := ms.ReadPath(inputPath) if err != nil { return nil, err @@ -176,6 +177,7 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, input return nil, err } + defer xdefer.Errorf(&err, "failed to compile d2 \nplease report this issue here: https://github.com/terrastruct/d2/issues/new") svg, err := d2svg.Render(d) if err != nil { return nil, err diff --git a/cmd/d2/watch.go b/cmd/d2/watch.go index 89b74da27..5194ce3fa 100644 --- a/cmd/d2/watch.go +++ b/cmd/d2/watch.go @@ -332,7 +332,6 @@ func (w *watcher) compileLoop(ctx context.Context) error { if filepath.Ext(w.outputPath) == ".png" && !w.pw.Browser.IsConnected() { newPW, err := w.pw.RestartBrowser() if err != nil { - err = fmt.Errorf("png exporter has disconnected") w.ms.Log.Error.Print(err) w.broadcast(&compileResult{ Err: err.Error(), diff --git a/lib/png/png.go b/lib/png/png.go index e1cc821f1..509faf6b9 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -12,6 +12,7 @@ import ( "github.com/playwright-community/playwright-go" "oss.terrastruct.com/d2/lib/xmain" + "oss.terrastruct.com/xdefer" ) type Playwright struct { @@ -21,6 +22,8 @@ type Playwright struct { } func (pw *Playwright) RestartBrowser() (newPW Playwright, err error) { + defer xdefer.Errorf(&err, "png exporter has disconnected\nplease report this issue here: https://github.com/terrastruct/d2/issues/new") + if err = pw.Browser.Close(); err != nil { return Playwright{}, err } @@ -28,6 +31,8 @@ func (pw *Playwright) RestartBrowser() (newPW Playwright, err error) { } func (pw *Playwright) Cleanup() (err error) { + defer xdefer.Errorf(&err, "failed to clean up png exporter\nplease report this issue here: https://github.com/terrastruct/d2/issues/new") + if err = pw.Browser.Close(); err != nil { return err } @@ -57,7 +62,9 @@ func startPlaywright(pw *playwright.Playwright) (Playwright, error) { }, nil } -func InitPlaywright() (Playwright, error) { +func InitPlaywright() (_ Playwright, err error) { + defer xdefer.Errorf(&err, "failed to initialize png exporter\nplease report this issue here: https://github.com/terrastruct/d2/issues/new") + // check if playwright driver/browsers are installed and up to date // https://github.com/playwright-community/playwright-go/blob/8e8f670b5fa7ba5365ae4bfc123fea4aac359763/run.go#L64. driver, err := playwright.NewDriver(&playwright.RunOptions{}) @@ -73,7 +80,7 @@ func InitPlaywright() (Playwright, error) { cmd := exec.Command(driver.DriverBinaryLocation, "--version") output, err := cmd.Output() if err != nil { - return Playwright{}, fmt.Errorf("error installing png exporter: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) + return Playwright{}, err } if !bytes.Contains(output, []byte(driver.Version)) { err = playwright.Install() @@ -82,7 +89,7 @@ func InitPlaywright() (Playwright, error) { } } } else { - return Playwright{}, fmt.Errorf("could not install png exporter: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) + return Playwright{}, err } pw, err := playwright.Run() @@ -96,8 +103,10 @@ func InitPlaywright() (Playwright, error) { var genPNGScript string func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage []byte, err error) { + defer xdefer.Errorf(&err, "failed to export png") + if page == nil { - return nil, fmt.Errorf("png exporter was not initialized properly\nplease report this issue here: https://github.com/terrastruct/d2/issues/new") + return nil, fmt.Errorf("png exporter was not initialized properly") } encodedSVG := base64.StdEncoding.EncodeToString(svg) @@ -112,7 +121,7 @@ func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage [ if len(pngString) > 50 { pngString = pngString[0:50] + "..." } - return nil, fmt.Errorf("invalid PNG: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", pngString) + return nil, fmt.Errorf("invalid PNG: %v", pngString) } splicedPNGString := pngString[len(pngPrefix):] return base64.StdEncoding.DecodeString(splicedPNGString) From 3dc999e12c85ef0e90ec876843302ed03cb49531 Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 18:19:00 -0800 Subject: [PATCH 17/30] Revert "Use xdefer to append issue creation message" This reverts commit 49f0a049fef58eb909c3017cd54ec399120958b0. --- cmd/d2/main.go | 6 ++---- cmd/d2/watch.go | 1 + lib/png/png.go | 19 +++++-------------- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/cmd/d2/main.go b/cmd/d2/main.go index 443c65acf..e423166bc 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -22,7 +22,6 @@ import ( "oss.terrastruct.com/d2/lib/png" "oss.terrastruct.com/d2/lib/version" "oss.terrastruct.com/d2/lib/xmain" - "oss.terrastruct.com/xdefer" ) func main() { @@ -123,7 +122,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) { defer func() { cleanupErr := pw.Cleanup() if cleanupErr != nil { - ms.Log.Error.Printf(cleanupErr.Error()) + ms.Log.Error.Printf("error cleaning up png exporter: %v", cleanupErr.Error()) } }() } @@ -156,7 +155,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) { return nil } -func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, inputPath, outputPath string, page playwright.Page) (_ []byte, err error) { +func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, inputPath, outputPath string, page playwright.Page) ([]byte, error) { input, err := ms.ReadPath(inputPath) if err != nil { return nil, err @@ -177,7 +176,6 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, input return nil, err } - defer xdefer.Errorf(&err, "failed to compile d2 \nplease report this issue here: https://github.com/terrastruct/d2/issues/new") svg, err := d2svg.Render(d) if err != nil { return nil, err diff --git a/cmd/d2/watch.go b/cmd/d2/watch.go index 5194ce3fa..89b74da27 100644 --- a/cmd/d2/watch.go +++ b/cmd/d2/watch.go @@ -332,6 +332,7 @@ func (w *watcher) compileLoop(ctx context.Context) error { if filepath.Ext(w.outputPath) == ".png" && !w.pw.Browser.IsConnected() { newPW, err := w.pw.RestartBrowser() if err != nil { + err = fmt.Errorf("png exporter has disconnected") w.ms.Log.Error.Print(err) w.broadcast(&compileResult{ Err: err.Error(), diff --git a/lib/png/png.go b/lib/png/png.go index 509faf6b9..e1cc821f1 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -12,7 +12,6 @@ import ( "github.com/playwright-community/playwright-go" "oss.terrastruct.com/d2/lib/xmain" - "oss.terrastruct.com/xdefer" ) type Playwright struct { @@ -22,8 +21,6 @@ type Playwright struct { } func (pw *Playwright) RestartBrowser() (newPW Playwright, err error) { - defer xdefer.Errorf(&err, "png exporter has disconnected\nplease report this issue here: https://github.com/terrastruct/d2/issues/new") - if err = pw.Browser.Close(); err != nil { return Playwright{}, err } @@ -31,8 +28,6 @@ func (pw *Playwright) RestartBrowser() (newPW Playwright, err error) { } func (pw *Playwright) Cleanup() (err error) { - defer xdefer.Errorf(&err, "failed to clean up png exporter\nplease report this issue here: https://github.com/terrastruct/d2/issues/new") - if err = pw.Browser.Close(); err != nil { return err } @@ -62,9 +57,7 @@ func startPlaywright(pw *playwright.Playwright) (Playwright, error) { }, nil } -func InitPlaywright() (_ Playwright, err error) { - defer xdefer.Errorf(&err, "failed to initialize png exporter\nplease report this issue here: https://github.com/terrastruct/d2/issues/new") - +func InitPlaywright() (Playwright, error) { // check if playwright driver/browsers are installed and up to date // https://github.com/playwright-community/playwright-go/blob/8e8f670b5fa7ba5365ae4bfc123fea4aac359763/run.go#L64. driver, err := playwright.NewDriver(&playwright.RunOptions{}) @@ -80,7 +73,7 @@ func InitPlaywright() (_ Playwright, err error) { cmd := exec.Command(driver.DriverBinaryLocation, "--version") output, err := cmd.Output() if err != nil { - return Playwright{}, err + return Playwright{}, fmt.Errorf("error installing png exporter: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) } if !bytes.Contains(output, []byte(driver.Version)) { err = playwright.Install() @@ -89,7 +82,7 @@ func InitPlaywright() (_ Playwright, err error) { } } } else { - return Playwright{}, err + return Playwright{}, fmt.Errorf("could not install png exporter: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) } pw, err := playwright.Run() @@ -103,10 +96,8 @@ func InitPlaywright() (_ Playwright, err error) { var genPNGScript string func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage []byte, err error) { - defer xdefer.Errorf(&err, "failed to export png") - if page == nil { - return nil, fmt.Errorf("png exporter was not initialized properly") + return nil, fmt.Errorf("png exporter was not initialized properly\nplease report this issue here: https://github.com/terrastruct/d2/issues/new") } encodedSVG := base64.StdEncoding.EncodeToString(svg) @@ -121,7 +112,7 @@ func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage [ if len(pngString) > 50 { pngString = pngString[0:50] + "..." } - return nil, fmt.Errorf("invalid PNG: %v", pngString) + return nil, fmt.Errorf("invalid PNG: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", pngString) } splicedPNGString := pngString[len(pngPrefix):] return base64.StdEncoding.DecodeString(splicedPNGString) From 6bec385811dda7538ed7ef32c86595b8bb4934a3 Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 18:23:44 -0800 Subject: [PATCH 18/30] TODO: somehow defer error reporting --- lib/png/png.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/png/png.go b/lib/png/png.go index e1cc821f1..b54f197be 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -73,7 +73,7 @@ func InitPlaywright() (Playwright, error) { cmd := exec.Command(driver.DriverBinaryLocation, "--version") output, err := cmd.Output() if err != nil { - return Playwright{}, fmt.Errorf("error installing png exporter: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) + return Playwright{}, fmt.Errorf("error getting png exporter version: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) } if !bytes.Contains(output, []byte(driver.Version)) { err = playwright.Install() @@ -103,7 +103,7 @@ func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage [ encodedSVG := base64.StdEncoding.EncodeToString(svg) pngInterface, err := page.Evaluate(genPNGScript, "data:image/svg+xml;charset=utf-8;base64,"+encodedSVG) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to generate png: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) } pngString := fmt.Sprintf("%v", pngInterface) From 3436936349042d67c9e13e2362ab10ee760fc92d Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 18:30:02 -0800 Subject: [PATCH 19/30] Remove maxWidth for png exports --- lib/png/generate_png.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/png/generate_png.js b/lib/png/generate_png.js index 99eccc61e..1b9f7d703 100644 --- a/lib/png/generate_png.js +++ b/lib/png/generate_png.js @@ -1,5 +1,4 @@ async (imgString) => { - const exportMaxWidth = 4000; const tempImg = new Image(); const loadImage = () => { return new Promise((resolve, reject) => { @@ -12,8 +11,8 @@ async (imgString) => { }; const img = await loadImage(); const canvas = document.createElement("canvas"); - canvas.width = Math.min(img.width, exportMaxWidth); - canvas.height = (canvas.width * img.height) / img.width; + canvas.width = img.width; + canvas.height = img.height; const ctx = canvas.getContext("2d"); if (!ctx) { return ""; From 269ace1d1c80b0c26796a3af595b71af10ad1816 Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 19:02:25 -0800 Subject: [PATCH 20/30] PR comments --- cmd/d2/main.go | 2 +- cmd/d2/watch.go | 6 +----- lib/png/generate_png.js | 2 +- lib/png/png.go | 14 +++++++------- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/cmd/d2/main.go b/cmd/d2/main.go index e423166bc..b21467b9e 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -122,7 +122,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) { defer func() { cleanupErr := pw.Cleanup() if cleanupErr != nil { - ms.Log.Error.Printf("error cleaning up png exporter: %v", cleanupErr.Error()) + ms.Log.Error.Printf("error cleaning up playwright: %v", cleanupErr.Error()) } }() } diff --git a/cmd/d2/watch.go b/cmd/d2/watch.go index 89b74da27..0559ce04f 100644 --- a/cmd/d2/watch.go +++ b/cmd/d2/watch.go @@ -332,11 +332,7 @@ func (w *watcher) compileLoop(ctx context.Context) error { if filepath.Ext(w.outputPath) == ".png" && !w.pw.Browser.IsConnected() { newPW, err := w.pw.RestartBrowser() if err != nil { - err = fmt.Errorf("png exporter has disconnected") - w.ms.Log.Error.Print(err) - w.broadcast(&compileResult{ - Err: err.Error(), - }) + return fmt.Errorf("playwright could not be restarted: %w", err) } w.pw = newPW } diff --git a/lib/png/generate_png.js b/lib/png/generate_png.js index 1b9f7d703..133cadba8 100644 --- a/lib/png/generate_png.js +++ b/lib/png/generate_png.js @@ -15,7 +15,7 @@ async (imgString) => { canvas.height = img.height; const ctx = canvas.getContext("2d"); if (!ctx) { - return ""; + return new Error("could not get canvas context"); } ctx.drawImage(img, 0, 0, canvas.width, canvas.height); return canvas.toDataURL("image/png"); diff --git a/lib/png/png.go b/lib/png/png.go index b54f197be..6c2c30632 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -27,11 +27,11 @@ func (pw *Playwright) RestartBrowser() (newPW Playwright, err error) { return startPlaywright(pw.PW) } -func (pw *Playwright) Cleanup() (err error) { - if err = pw.Browser.Close(); err != nil { +func (pw *Playwright) Cleanup() error { + if err := pw.Browser.Close(); err != nil { return err } - if err = pw.PW.Stop(); err != nil { + if err := pw.PW.Stop(); err != nil { return err } return nil @@ -73,7 +73,7 @@ func InitPlaywright() (Playwright, error) { cmd := exec.Command(driver.DriverBinaryLocation, "--version") output, err := cmd.Output() if err != nil { - return Playwright{}, fmt.Errorf("error getting png exporter version: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) + return Playwright{}, fmt.Errorf("error getting playwright version: %w\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err) } if !bytes.Contains(output, []byte(driver.Version)) { err = playwright.Install() @@ -82,7 +82,7 @@ func InitPlaywright() (Playwright, error) { } } } else { - return Playwright{}, fmt.Errorf("could not install png exporter: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) + return Playwright{}, fmt.Errorf("could not access playwright binary location: %w\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err) } pw, err := playwright.Run() @@ -97,13 +97,13 @@ var genPNGScript string func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage []byte, err error) { if page == nil { - return nil, fmt.Errorf("png exporter was not initialized properly\nplease report this issue here: https://github.com/terrastruct/d2/issues/new") + return nil, fmt.Errorf("playwright was not initialized properly\nplease report this issue here: https://github.com/terrastruct/d2/issues/new") } encodedSVG := base64.StdEncoding.EncodeToString(svg) pngInterface, err := page.Evaluate(genPNGScript, "data:image/svg+xml;charset=utf-8;base64,"+encodedSVG) if err != nil { - return nil, fmt.Errorf("failed to generate png: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err.Error()) + return nil, fmt.Errorf("failed to generate png: %w\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err) } pngString := fmt.Sprintf("%v", pngInterface) From 8b453eae9a1c9f3219de1c12a1c2773d634176a2 Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Thu, 17 Nov 2022 20:39:10 -0800 Subject: [PATCH 21/30] PR comments --- cmd/d2/main.go | 2 +- cmd/d2/watch.go | 2 +- lib/png/png.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/d2/main.go b/cmd/d2/main.go index b21467b9e..7aeecf923 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -122,7 +122,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) { defer func() { cleanupErr := pw.Cleanup() if cleanupErr != nil { - ms.Log.Error.Printf("error cleaning up playwright: %v", cleanupErr.Error()) + ms.Log.Error.Printf("error cleaning up Playwright: %v", cleanupErr.Error()) } }() } diff --git a/cmd/d2/watch.go b/cmd/d2/watch.go index 0559ce04f..98a05bbdd 100644 --- a/cmd/d2/watch.go +++ b/cmd/d2/watch.go @@ -332,7 +332,7 @@ func (w *watcher) compileLoop(ctx context.Context) error { if filepath.Ext(w.outputPath) == ".png" && !w.pw.Browser.IsConnected() { newPW, err := w.pw.RestartBrowser() if err != nil { - return fmt.Errorf("playwright could not be restarted: %w", err) + return fmt.Errorf("Playwright could not be restarted: %w", err) } w.pw = newPW } diff --git a/lib/png/png.go b/lib/png/png.go index 6c2c30632..90b947800 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -73,7 +73,7 @@ func InitPlaywright() (Playwright, error) { cmd := exec.Command(driver.DriverBinaryLocation, "--version") output, err := cmd.Output() if err != nil { - return Playwright{}, fmt.Errorf("error getting playwright version: %w\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err) + return Playwright{}, fmt.Errorf("error getting Playwright version: %w\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err) } if !bytes.Contains(output, []byte(driver.Version)) { err = playwright.Install() @@ -82,7 +82,7 @@ func InitPlaywright() (Playwright, error) { } } } else { - return Playwright{}, fmt.Errorf("could not access playwright binary location: %w\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err) + return Playwright{}, fmt.Errorf("could not access Playwright binary location: %v\nerror: %w\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", driver.DriverBinaryLocation, err) } pw, err := playwright.Run() @@ -97,7 +97,7 @@ var genPNGScript string func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage []byte, err error) { if page == nil { - return nil, fmt.Errorf("playwright was not initialized properly\nplease report this issue here: https://github.com/terrastruct/d2/issues/new") + return nil, fmt.Errorf("Playwright was not initialized properly\nplease report this issue here: https://github.com/terrastruct/d2/issues/new") } encodedSVG := base64.StdEncoding.EncodeToString(svg) From 0f95288b5ca16a5664876789de0063090ad535d6 Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Fri, 18 Nov 2022 13:29:30 -0800 Subject: [PATCH 22/30] PR comments --- cmd/d2/main.go | 2 +- cmd/d2/watch.go | 5 +++++ lib/png/png.go | 38 +++++++++++++++++++------------------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/cmd/d2/main.go b/cmd/d2/main.go index 7aeecf923..e8108cb74 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -186,7 +186,7 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, input } if filepath.Ext(outputPath) == ".png" { - outputImage, err = png.ExportPNG(ms, page, outputImage) + outputImage, err = png.ConvertSVG(ms, page, outputImage) if err != nil { return nil, err } diff --git a/cmd/d2/watch.go b/cmd/d2/watch.go index 98a05bbdd..8207641de 100644 --- a/cmd/d2/watch.go +++ b/cmd/d2/watch.go @@ -332,6 +332,11 @@ func (w *watcher) compileLoop(ctx context.Context) error { if filepath.Ext(w.outputPath) == ".png" && !w.pw.Browser.IsConnected() { newPW, err := w.pw.RestartBrowser() if err != nil { + broadcastErr := fmt.Errorf("issue encountered with PNG exporter, stopping") + w.ms.Log.Error.Print(broadcastErr) + w.broadcast(&compileResult{ + Err: broadcastErr.Error(), + }) return fmt.Errorf("Playwright could not be restarted: %w", err) } w.pw = newPW diff --git a/lib/png/png.go b/lib/png/png.go index 90b947800..9a8d0d888 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -64,25 +64,28 @@ func InitPlaywright() (Playwright, error) { if err != nil { return Playwright{}, err } - if _, err := os.Stat(driver.DriverBinaryLocation); os.IsNotExist(err) { - err = playwright.Install() - if err != nil { - return Playwright{}, err - } - } else if err == nil { - cmd := exec.Command(driver.DriverBinaryLocation, "--version") - output, err := cmd.Output() - if err != nil { - return Playwright{}, fmt.Errorf("error getting Playwright version: %w\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err) - } - if !bytes.Contains(output, []byte(driver.Version)) { + _, err = os.Stat(driver.DriverBinaryLocation) + if err != nil { + if os.IsNotExist(err) { err = playwright.Install() if err != nil { return Playwright{}, err } + } else { + return Playwright{}, fmt.Errorf("could not access Playwright binary location: %v\nerror: %w\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", driver.DriverBinaryLocation, err) + } + } + + cmd := exec.Command(driver.DriverBinaryLocation, "--version") + output, err := cmd.Output() + if err != nil { + return Playwright{}, fmt.Errorf("error getting Playwright version: %w\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err) + } + if !bytes.Contains(output, []byte(driver.Version)) { + err = playwright.Install() + if err != nil { + return Playwright{}, err } - } else { - return Playwright{}, fmt.Errorf("could not access Playwright binary location: %v\nerror: %w\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", driver.DriverBinaryLocation, err) } pw, err := playwright.Run() @@ -95,11 +98,9 @@ func InitPlaywright() (Playwright, error) { //go:embed generate_png.js var genPNGScript string -func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage []byte, err error) { - if page == nil { - return nil, fmt.Errorf("Playwright was not initialized properly\nplease report this issue here: https://github.com/terrastruct/d2/issues/new") - } +const pngPrefix = "data:image/png;base64," +func ConvertSVG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage []byte, err error) { encodedSVG := base64.StdEncoding.EncodeToString(svg) pngInterface, err := page.Evaluate(genPNGScript, "data:image/svg+xml;charset=utf-8;base64,"+encodedSVG) if err != nil { @@ -107,7 +108,6 @@ func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage [ } pngString := fmt.Sprintf("%v", pngInterface) - pngPrefix := "data:image/png;base64," if !strings.HasPrefix(pngString, pngPrefix) { if len(pngString) > 50 { pngString = pngString[0:50] + "..." From 24aa08bb2bd154d35a3a15e8ee13b1cbed296d6d Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Mon, 21 Nov 2022 10:05:48 -0800 Subject: [PATCH 23/30] Update lib/png/png.go Co-authored-by: Anmol Sethi --- lib/png/png.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/png/png.go b/lib/png/png.go index 9a8d0d888..a3fd64792 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -112,7 +112,7 @@ func ConvertSVG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage if len(pngString) > 50 { pngString = pngString[0:50] + "..." } - return nil, fmt.Errorf("invalid PNG: %v\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", pngString) + return nil, fmt.Errorf("invalid PNG: %q\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", pngString) } splicedPNGString := pngString[len(pngPrefix):] return base64.StdEncoding.DecodeString(splicedPNGString) From 233fc76e7f6399e86f3e3ed77686b725b9570c96 Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Mon, 21 Nov 2022 10:46:54 -0800 Subject: [PATCH 24/30] PR comments --- ci/release/template/man/d2.1 | 10 +++++-- cmd/d2/help.go | 2 +- cmd/d2/main.go | 17 ++++++------ cmd/d2/watch.go | 2 +- lib/png/png.go | 52 +++++++++--------------------------- 5 files changed, 31 insertions(+), 52 deletions(-) diff --git a/ci/release/template/man/d2.1 b/ci/release/template/man/d2.1 index 6d548cb0f..faf28e1fa 100644 --- a/ci/release/template/man/d2.1 +++ b/ci/release/template/man/d2.1 @@ -9,7 +9,9 @@ .Op Fl -watch Ar false .Op Fl -theme Em 0 .Ar file.d2 -.Op Ar file.svg|file.png +.Op Ar file.svg +| +.Op Ar file.png .Nm d2 .Op Fl -watch Ar false .Op Fl -theme Em 0 @@ -21,7 +23,11 @@ .Nm compiles and renders .Ar file.d2 -to svg or png. +to +.Ar file.svg +, +.Ar file.png +.Ns . .Pp Pass - to have .Nm diff --git a/cmd/d2/help.go b/cmd/d2/help.go index 69e33bb98..6531f1379 100644 --- a/cmd/d2/help.go +++ b/cmd/d2/help.go @@ -17,7 +17,7 @@ func help(ms *xmain.State) { fmt.Fprintf(ms.Stdout, `Usage: %s [--watch=false] [--theme=0] file.d2 [file.svg|file.png] -%[1]s compiles and renders file.d2 to svg or png. +%[1]s compiles and renders file.d2 to file.svg, file.png. Use - to have d2 read from stdin or write to stdout. Flags: diff --git a/cmd/d2/main.go b/cmd/d2/main.go index e8108cb74..fb21c1bfa 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -119,11 +119,12 @@ func run(ctx context.Context, ms *xmain.State) (err error) { if err != nil { return err } - defer func() { - cleanupErr := pw.Cleanup() - if cleanupErr != nil { - ms.Log.Error.Printf("error cleaning up Playwright: %v", cleanupErr.Error()) + defer func() error { + err = pw.Cleanup() + if err != nil { + return err } + return nil }() } @@ -180,23 +181,23 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, input if err != nil { return nil, err } - outputImage, err := plugin.PostProcess(ctx, svg) + out, err := plugin.PostProcess(ctx, svg) if err != nil { return nil, err } if filepath.Ext(outputPath) == ".png" { - outputImage, err = png.ConvertSVG(ms, page, outputImage) + out, err = png.ConvertSVG(ms, page, out) if err != nil { return nil, err } } - err = ms.WritePath(outputPath, outputImage) + err = ms.WritePath(outputPath, out) if err != nil { return nil, err } - return svg, nil + return out, nil } // newExt must include leading . diff --git a/cmd/d2/watch.go b/cmd/d2/watch.go index 8207641de..1680a2521 100644 --- a/cmd/d2/watch.go +++ b/cmd/d2/watch.go @@ -337,7 +337,7 @@ func (w *watcher) compileLoop(ctx context.Context) error { w.broadcast(&compileResult{ Err: broadcastErr.Error(), }) - return fmt.Errorf("Playwright could not be restarted: %w", err) + continue } w.pw = newPW } diff --git a/lib/png/png.go b/lib/png/png.go index a3fd64792..52fe23a7f 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -1,11 +1,8 @@ package png import ( - "bytes" "encoding/base64" "fmt" - "os" - "os/exec" "strings" _ "embed" @@ -20,19 +17,19 @@ type Playwright struct { Page playwright.Page } -func (pw *Playwright) RestartBrowser() (newPW Playwright, err error) { - if err = pw.Browser.Close(); err != nil { - return Playwright{}, err +func (pw *Playwright) RestartBrowser() (Playwright, error) { + if err := pw.Browser.Close(); err != nil { + return Playwright{}, fmt.Errorf("failed to close Playwright browser: %w", err) } return startPlaywright(pw.PW) } func (pw *Playwright) Cleanup() error { if err := pw.Browser.Close(); err != nil { - return err + return fmt.Errorf("failed to close Playwright browser: %w", err) } if err := pw.PW.Stop(); err != nil { - return err + return fmt.Errorf("failed to stop Playwright: %w", err) } return nil } @@ -40,15 +37,15 @@ func (pw *Playwright) Cleanup() error { func startPlaywright(pw *playwright.Playwright) (Playwright, error) { browser, err := pw.Chromium.Launch() if err != nil { - return Playwright{}, err + return Playwright{}, fmt.Errorf("failed to launch Chromium: %w", err) } context, err := browser.NewContext() if err != nil { - return Playwright{}, err + return Playwright{}, fmt.Errorf("failed to start new Playwright browser context: %w", err) } page, err := context.NewPage() if err != nil { - return Playwright{}, err + return Playwright{}, fmt.Errorf("failed to start new Playwright page: %w", err) } return Playwright{ PW: pw, @@ -58,39 +55,14 @@ func startPlaywright(pw *playwright.Playwright) (Playwright, error) { } func InitPlaywright() (Playwright, error) { - // check if playwright driver/browsers are installed and up to date - // https://github.com/playwright-community/playwright-go/blob/8e8f670b5fa7ba5365ae4bfc123fea4aac359763/run.go#L64. - driver, err := playwright.NewDriver(&playwright.RunOptions{}) + err := playwright.Install(&playwright.RunOptions{Verbose: false}) if err != nil { - return Playwright{}, err - } - _, err = os.Stat(driver.DriverBinaryLocation) - if err != nil { - if os.IsNotExist(err) { - err = playwright.Install() - if err != nil { - return Playwright{}, err - } - } else { - return Playwright{}, fmt.Errorf("could not access Playwright binary location: %v\nerror: %w\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", driver.DriverBinaryLocation, err) - } - } - - cmd := exec.Command(driver.DriverBinaryLocation, "--version") - output, err := cmd.Output() - if err != nil { - return Playwright{}, fmt.Errorf("error getting Playwright version: %w\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err) - } - if !bytes.Contains(output, []byte(driver.Version)) { - err = playwright.Install() - if err != nil { - return Playwright{}, err - } + return Playwright{}, fmt.Errorf("failed to install Playwright: %w", err) } pw, err := playwright.Run() if err != nil { - return Playwright{}, err + return Playwright{}, fmt.Errorf("failed to run Playwright: %w", err) } return startPlaywright(pw) } @@ -100,7 +72,7 @@ var genPNGScript string const pngPrefix = "data:image/png;base64," -func ConvertSVG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage []byte, err error) { +func ConvertSVG(ms *xmain.State, page playwright.Page, svg []byte) ([]byte, error) { encodedSVG := base64.StdEncoding.EncodeToString(svg) pngInterface, err := page.Evaluate(genPNGScript, "data:image/svg+xml;charset=utf-8;base64,"+encodedSVG) if err != nil { From 27b7a5a2a961dbce8638165d3c07269e8b1e2dd2 Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Mon, 21 Nov 2022 10:51:46 -0800 Subject: [PATCH 25/30] broadcast error from RestartBrowser --- cmd/d2/watch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/d2/watch.go b/cmd/d2/watch.go index 1680a2521..669a24a6e 100644 --- a/cmd/d2/watch.go +++ b/cmd/d2/watch.go @@ -332,7 +332,7 @@ func (w *watcher) compileLoop(ctx context.Context) error { if filepath.Ext(w.outputPath) == ".png" && !w.pw.Browser.IsConnected() { newPW, err := w.pw.RestartBrowser() if err != nil { - broadcastErr := fmt.Errorf("issue encountered with PNG exporter, stopping") + broadcastErr := fmt.Errorf("issue encountered with PNG exporter: %w", err) w.ms.Log.Error.Print(broadcastErr) w.broadcast(&compileResult{ Err: broadcastErr.Error(), From 8b53a86345b418b3f08e622e2eaac947b26242c2 Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Mon, 21 Nov 2022 11:02:17 -0800 Subject: [PATCH 26/30] Switch , to | --- ci/release/template/man/d2.1 | 2 +- cmd/d2/help.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 4a5ca14fc..a5407102c 100644 --- a/ci/release/template/man/d2.1 +++ b/ci/release/template/man/d2.1 @@ -25,7 +25,7 @@ compiles and renders .Ar file.d2 to .Ar file.svg -, +| .Ar file.png .Ns . .Pp diff --git a/cmd/d2/help.go b/cmd/d2/help.go index 33ac3ad16..98390495d 100644 --- a/cmd/d2/help.go +++ b/cmd/d2/help.go @@ -17,7 +17,7 @@ func help(ms *xmain.State) { fmt.Fprintf(ms.Stdout, `Usage: %s [--watch=false] [--theme=0] file.d2 [file.svg|file.png] -%[1]s compiles and renders file.d2 to file.svg, file.png. +%[1]s compiles and renders file.d2 to file.svg|file.png. Use - to have d2 read from stdin or write to stdout. Flags: From 794df257a7e99a9c01ab5d89502c510eb39bbeae Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Mon, 21 Nov 2022 11:05:10 -0800 Subject: [PATCH 27/30] typecast, fix fmt --- go.mod | 2 +- lib/png/png.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index ad2069c4d..c6a3a9e17 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 github.com/mazznoer/csscolorparser v0.1.3 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 + github.com/playwright-community/playwright-go v0.2000.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.7.0 github.com/yuin/goldmark v1.5.2 @@ -47,7 +48,6 @@ require ( github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/playwright-community/playwright-go v0.2000.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/ugorji/go/codec v1.2.6 // indirect diff --git a/lib/png/png.go b/lib/png/png.go index 52fe23a7f..c72e4fa91 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -8,6 +8,7 @@ import ( _ "embed" "github.com/playwright-community/playwright-go" + "oss.terrastruct.com/d2/lib/xmain" ) @@ -79,7 +80,7 @@ func ConvertSVG(ms *xmain.State, page playwright.Page, svg []byte) ([]byte, erro return nil, fmt.Errorf("failed to generate png: %w\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err) } - pngString := fmt.Sprintf("%v", pngInterface) + pngString := pngInterface.(string) if !strings.HasPrefix(pngString, pngPrefix) { if len(pngString) > 50 { pngString = pngString[0:50] + "..." From cb6d3c298d4e9fb730d30c8761627749e504ed9c Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Mon, 21 Nov 2022 11:09:41 -0800 Subject: [PATCH 28/30] PR comments --- cmd/d2/main.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/d2/main.go b/cmd/d2/main.go index e18f5ce76..3a26705e4 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -202,13 +202,14 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, theme if err != nil { return nil, err } - out, err := plugin.PostProcess(ctx, svg) + svg, err = plugin.PostProcess(ctx, svg) if err != nil { return nil, err } + out := svg if filepath.Ext(outputPath) == ".png" { - out, err = png.ConvertSVG(ms, page, out) + out, err = png.ConvertSVG(ms, page, svg) if err != nil { return nil, err } From ab40e9a900e9f486da8a4fc5a1e95a34aeac3e2c Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Mon, 21 Nov 2022 11:22:50 -0800 Subject: [PATCH 29/30] Remove extraneous new line --- cmd/d2/main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/d2/main.go b/cmd/d2/main.go index 3a26705e4..9adabe9f9 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -173,7 +173,6 @@ func run(ctx context.Context, ms *xmain.State) (err error) { if err != nil { return err } - ms.Log.Success.Printf("successfully compiled %v to %v", inputPath, outputPath) return nil } From b99b7416512cf815163f9759f62cc35a866306ad Mon Sep 17 00:00:00 2001 From: Bernard Xie Date: Mon, 21 Nov 2022 13:24:10 -0800 Subject: [PATCH 30/30] PR comments --- ci/release/changelogs/next.md | 2 +- ci/release/template/man/d2.1 | 2 +- cmd/d2/main.go | 9 ++++----- lib/png/png.go | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 1fab4e6f8..24c43152b 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -2,8 +2,8 @@ For v0.0.99 we focused on X, Y and Z. Enjoy! #### Features 💸 +- Ability to export to PNG - Now you can easily do x, y and z #9999 -- Ability to export to PNGs #### Improvements 🔧 diff --git a/ci/release/template/man/d2.1 b/ci/release/template/man/d2.1 index a5407102c..1f86ecb6a 100644 --- a/ci/release/template/man/d2.1 +++ b/ci/release/template/man/d2.1 @@ -23,7 +23,7 @@ .Nm compiles and renders .Ar file.d2 -to +to .Ar file.svg | .Ar file.png diff --git a/cmd/d2/main.go b/cmd/d2/main.go index 9adabe9f9..a5e5c39e2 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -133,12 +133,11 @@ func run(ctx context.Context, ms *xmain.State) (err error) { if err != nil { return err } - defer func() error { - err = pw.Cleanup() - if err != nil { - return err + defer func() { + cleanupErr := pw.Cleanup() + if err == nil { + err = cleanupErr } - return nil }() } diff --git a/lib/png/png.go b/lib/png/png.go index c72e4fa91..0199c3c91 100644 --- a/lib/png/png.go +++ b/lib/png/png.go @@ -77,7 +77,7 @@ func ConvertSVG(ms *xmain.State, page playwright.Page, svg []byte) ([]byte, erro encodedSVG := base64.StdEncoding.EncodeToString(svg) pngInterface, err := page.Evaluate(genPNGScript, "data:image/svg+xml;charset=utf-8;base64,"+encodedSVG) if err != nil { - return nil, fmt.Errorf("failed to generate png: %w\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", err) + return nil, fmt.Errorf("failed to generate png: %w", err) } pngString := pngInterface.(string) @@ -85,7 +85,7 @@ func ConvertSVG(ms *xmain.State, page playwright.Page, svg []byte) ([]byte, erro if len(pngString) > 50 { pngString = pngString[0:50] + "..." } - return nil, fmt.Errorf("invalid PNG: %q\nplease report this issue here: https://github.com/terrastruct/d2/issues/new", pngString) + return nil, fmt.Errorf("invalid PNG: %q", pngString) } splicedPNGString := pngString[len(pngPrefix):] return base64.StdEncoding.DecodeString(splicedPNGString)