d2/lib/png/png.go

156 lines
3.9 KiB
Go
Raw Normal View History

2022-11-17 01:41:53 +00:00
package png
import (
2023-03-14 00:52:50 +00:00
"bytes"
2022-11-17 01:41:53 +00:00
"encoding/base64"
"fmt"
"strings"
"time"
2022-11-17 01:41:53 +00:00
_ "embed"
2023-03-14 00:52:50 +00:00
exif "github.com/dsoprea/go-exif/v3"
exifcommon "github.com/dsoprea/go-exif/v3/common"
pngstruct "github.com/dsoprea/go-png-image-structure/v2"
2022-11-17 01:41:53 +00:00
"github.com/playwright-community/playwright-go"
2022-11-21 19:05:10 +00:00
"oss.terrastruct.com/d2/lib/background"
2023-03-14 00:58:47 +00:00
"oss.terrastruct.com/d2/lib/version"
2022-12-01 19:19:39 +00:00
"oss.terrastruct.com/util-go/xmain"
2022-11-17 01:41:53 +00:00
)
2023-04-11 13:25:04 +00:00
// ConvertSVG scales the image by 2x
const SCALE = 2.
type Playwright struct {
2022-11-17 22:55:43 +00:00
PW *playwright.Playwright
Browser playwright.Browser
Page playwright.Page
}
2022-11-21 18:46:54 +00:00
func (pw *Playwright) RestartBrowser() (Playwright, error) {
if err := pw.Browser.Close(); err != nil {
return Playwright{}, fmt.Errorf("failed to close Playwright browser: %w", err)
}
2022-11-18 00:41:28 +00:00
return startPlaywright(pw.PW)
}
2022-11-18 03:02:25 +00:00
func (pw *Playwright) Cleanup() error {
if err := pw.Browser.Close(); err != nil {
2022-11-21 18:46:54 +00:00
return fmt.Errorf("failed to close Playwright browser: %w", err)
2022-11-18 00:41:28 +00:00
}
2022-11-18 03:02:25 +00:00
if err := pw.PW.Stop(); err != nil {
2022-11-21 18:46:54 +00:00
return fmt.Errorf("failed to stop Playwright: %w", err)
2022-11-18 00:41:28 +00:00
}
return nil
}
func startPlaywright(pw *playwright.Playwright) (Playwright, error) {
browser, err := pw.Chromium.Launch()
if err != nil {
2022-11-21 18:46:54 +00:00
return Playwright{}, fmt.Errorf("failed to launch Chromium: %w", err)
}
context, err := browser.NewContext()
if err != nil {
2022-11-21 18:46:54 +00:00
return Playwright{}, fmt.Errorf("failed to start new Playwright browser context: %w", err)
}
page, err := context.NewPage()
if err != nil {
2022-11-21 18:46:54 +00:00
return Playwright{}, fmt.Errorf("failed to start new Playwright page: %w", err)
}
return Playwright{
2022-11-18 00:41:28 +00:00
PW: pw,
2022-11-17 22:55:43 +00:00
Browser: browser,
Page: page,
}, nil
}
func InitPlaywright() (Playwright, error) {
err := playwright.Install(&playwright.RunOptions{
Verbose: false,
Browsers: []string{"chromium"},
})
2022-11-18 21:29:30 +00:00
if err != nil {
2022-11-21 18:46:54 +00:00
return Playwright{}, fmt.Errorf("failed to install Playwright: %w", err)
2022-11-17 01:41:53 +00:00
}
pw, err := playwright.Run()
if err != nil {
2022-11-21 18:46:54 +00:00
return Playwright{}, fmt.Errorf("failed to run Playwright: %w", err)
2022-11-17 01:41:53 +00:00
}
2022-11-18 00:41:28 +00:00
return startPlaywright(pw)
2022-11-17 01:41:53 +00:00
}
//go:embed generate_png.js
var genPNGScript string
2022-11-18 21:29:30 +00:00
const pngPrefix = "data:image/png;base64,"
2023-04-07 21:13:53 +00:00
// ConvertSVG converts the given SVG into a PNG.
// Note that the resulting PNG has 2x the size (width and height) of the original SVG (see generate_png.js)
2022-11-21 18:46:54 +00:00
func ConvertSVG(ms *xmain.State, page playwright.Page, svg []byte) ([]byte, error) {
cancel := background.Repeat(func() {
ms.Log.Info.Printf("converting to PNG...")
}, time.Second*5)
defer cancel()
2022-11-17 01:41:53 +00:00
encodedSVG := base64.StdEncoding.EncodeToString(svg)
2023-04-11 17:38:44 +00:00
pngInterface, err := page.Evaluate(genPNGScript, map[string]interface{}{
"imgString": "data:image/svg+xml;charset=utf-8;base64," + encodedSVG,
"scale": int(SCALE),
})
2022-11-17 01:41:53 +00:00
if err != nil {
2022-11-21 21:24:10 +00:00
return nil, fmt.Errorf("failed to generate png: %w", err)
2022-11-17 01:41:53 +00:00
}
2022-11-21 19:05:10 +00:00
pngString := pngInterface.(string)
2022-11-17 01:41:53 +00:00
if !strings.HasPrefix(pngString, pngPrefix) {
2022-11-17 22:24:59 +00:00
if len(pngString) > 50 {
pngString = pngString[0:50] + "..."
2022-11-17 22:13:23 +00:00
}
2022-11-21 21:24:10 +00:00
return nil, fmt.Errorf("invalid PNG: %q", pngString)
2022-11-17 01:41:53 +00:00
}
splicedPNGString := pngString[len(pngPrefix):]
return base64.StdEncoding.DecodeString(splicedPNGString)
2022-11-17 01:41:53 +00:00
}
2023-03-14 00:52:50 +00:00
func AddExif(png []byte) ([]byte, error) {
// https://pkg.go.dev/github.com/dsoprea/go-png-image-structure/v2?utm_source=godoc#example-ChunkSlice.SetExif
im, err := exifcommon.NewIfdMappingWithStandard()
if err != nil {
return nil, err
}
ti := exif.NewTagIndex()
ib := exif.NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder)
2023-03-14 00:58:47 +00:00
err = ib.AddStandardWithName("Make", "D2")
2023-03-14 00:52:50 +00:00
if err != nil {
return nil, err
}
2023-03-14 00:58:47 +00:00
err = ib.AddStandardWithName("Model", version.Version)
2023-03-14 00:52:50 +00:00
if err != nil {
return nil, err
}
pmp := pngstruct.NewPngMediaParser()
intfc, err := pmp.ParseBytes(png)
if err != nil {
return nil, err
}
cs := intfc.(*pngstruct.ChunkSlice)
err = cs.SetExif(ib)
if err != nil {
return nil, err
}
b := new(bytes.Buffer)
err = cs.WriteTo(b)
if err != nil {
return nil, err
}
return b.Bytes(), nil
}