forked from terrastruct/d2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpng.go
155 lines (132 loc) · 3.9 KB
/
png.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package png
import (
"bytes"
"encoding/base64"
"fmt"
"strings"
"time"
_ "embed"
exif "github.com/dsoprea/go-exif/v3"
exifcommon "github.com/dsoprea/go-exif/v3/common"
pngstruct "github.com/dsoprea/go-png-image-structure/v2"
"github.com/playwright-community/playwright-go"
"oss.terrastruct.com/d2/lib/background"
"oss.terrastruct.com/d2/lib/version"
"oss.terrastruct.com/util-go/xmain"
)
// ConvertSVG scales the image by 2x
const SCALE = 2.
type Playwright struct {
PW *playwright.Playwright
Browser playwright.Browser
Page playwright.Page
}
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 fmt.Errorf("failed to close Playwright browser: %w", err)
}
if err := pw.PW.Stop(); err != nil {
return fmt.Errorf("failed to stop Playwright: %w", err)
}
return nil
}
func startPlaywright(pw *playwright.Playwright) (Playwright, error) {
browser, err := pw.Chromium.Launch()
if err != nil {
return Playwright{}, fmt.Errorf("failed to launch Chromium: %w", err)
}
context, err := browser.NewContext()
if err != nil {
return Playwright{}, fmt.Errorf("failed to start new Playwright browser context: %w", err)
}
page, err := context.NewPage()
if err != nil {
return Playwright{}, fmt.Errorf("failed to start new Playwright page: %w", err)
}
return Playwright{
PW: pw,
Browser: browser,
Page: page,
}, nil
}
func InitPlaywright() (Playwright, error) {
err := playwright.Install(&playwright.RunOptions{
Verbose: false,
Browsers: []string{"chromium"},
})
if err != nil {
return Playwright{}, fmt.Errorf("failed to install Playwright: %w", err)
}
pw, err := playwright.Run()
if err != nil {
return Playwright{}, fmt.Errorf("failed to run Playwright: %w", err)
}
return startPlaywright(pw)
}
//go:embed generate_png.js
var genPNGScript string
const pngPrefix = "data:image/png;base64,"
// 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)
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()
encodedSVG := base64.StdEncoding.EncodeToString(svg)
pngInterface, err := page.Evaluate(genPNGScript, map[string]interface{}{
"imgString": "data:image/svg+xml;charset=utf-8;base64," + encodedSVG,
"scale": int(SCALE),
})
if err != nil {
return nil, fmt.Errorf("failed to generate png: %w", err)
}
pngString := pngInterface.(string)
if !strings.HasPrefix(pngString, pngPrefix) {
if len(pngString) > 50 {
pngString = pngString[0:50] + "..."
}
return nil, fmt.Errorf("invalid PNG: %q", pngString)
}
splicedPNGString := pngString[len(pngPrefix):]
return base64.StdEncoding.DecodeString(splicedPNGString)
}
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)
err = ib.AddStandardWithName("Make", "D2")
if err != nil {
return nil, err
}
err = ib.AddStandardWithName("Model", version.Version)
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
}