d2/lib/imgbundler/imgbundler.go

134 lines
2.7 KiB
Go
Raw Normal View History

2022-11-26 23:26:14 +00:00
package imgbundler
import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
2022-11-28 21:25:40 +00:00
"net/url"
2022-11-27 02:14:41 +00:00
"os"
2022-11-26 23:26:14 +00:00
"regexp"
"strings"
"sync"
"time"
"oss.terrastruct.com/d2/lib/xmain"
)
2022-11-28 21:25:40 +00:00
var imageRe = regexp.MustCompile(`<image href="([^"]+)"`)
2022-11-26 23:26:14 +00:00
type resp struct {
srctxt string
data string
err error
}
2022-11-27 02:14:41 +00:00
func InlineLocal(ms *xmain.State, in []byte) ([]byte, error) {
2022-11-28 21:25:40 +00:00
return inline(ms, in, false)
2022-11-27 02:14:41 +00:00
}
func InlineRemote(ms *xmain.State, in []byte) ([]byte, error) {
2022-11-28 21:25:40 +00:00
return inline(ms, in, true)
2022-11-27 02:14:41 +00:00
}
2022-11-29 19:01:51 +00:00
func inline(ms *xmain.State, svg []byte, isRemote bool) ([]byte, error) {
imgs := imageRe.FindAllSubmatch(svg, -1)
2022-11-28 21:25:40 +00:00
var filtered [][]string
for _, img := range imgs {
2022-11-29 19:01:51 +00:00
u, err := url.Parse(string(img[1]))
2022-11-28 21:25:40 +00:00
isRemoteImg := err == nil && strings.HasPrefix(u.Scheme, "http")
if isRemoteImg == isRemote {
2022-11-29 19:15:57 +00:00
filtered = append(filtered, []string{string(img[0]), string(img[1])})
2022-11-28 21:25:40 +00:00
}
}
2022-11-26 23:26:14 +00:00
var wg sync.WaitGroup
respChan := make(chan resp)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
2022-11-28 21:25:40 +00:00
wg.Add(len(filtered))
for _, img := range filtered {
2022-11-29 18:20:32 +00:00
go func(src, href string) {
var data string
var err error
if isRemote {
data, err = fetch(ctx, href)
} else {
data, err = read(ctx, href)
}
respChan <- resp{
srctxt: src,
data: data,
err: err,
}
}(img[0], img[1])
2022-11-26 23:26:14 +00:00
}
2022-11-29 19:01:51 +00:00
out := string(svg)
2022-11-26 23:26:14 +00:00
go func() {
for {
select {
case resp, ok := <-respChan:
if !ok {
return
}
if resp.err != nil {
2022-11-29 16:40:05 +00:00
ms.Log.Error.Printf("image failed to fetch: %v", resp.err)
2022-11-26 23:26:14 +00:00
} else {
2022-11-29 19:01:51 +00:00
out = strings.Replace(out, resp.srctxt, fmt.Sprintf(`<image href="%s"`, resp.data), 1)
2022-11-26 23:26:14 +00:00
}
wg.Done()
}
}
}()
wg.Wait()
close(respChan)
2022-11-29 19:01:51 +00:00
return []byte(out), nil
2022-11-26 23:26:14 +00:00
}
var transport = http.DefaultTransport
2022-11-29 18:20:32 +00:00
func fetch(ctx context.Context, href string) (string, error) {
2022-11-26 23:26:14 +00:00
req, err := http.NewRequestWithContext(ctx, "GET", href, nil)
if err != nil {
2022-11-29 18:20:32 +00:00
return "", err
2022-11-26 23:26:14 +00:00
}
client := &http.Client{Transport: transport}
imgResp, err := client.Do(req)
if err != nil {
2022-11-29 18:20:32 +00:00
return "", err
2022-11-26 23:26:14 +00:00
}
defer imgResp.Body.Close()
data, err := ioutil.ReadAll(imgResp.Body)
if err != nil {
2022-11-29 18:20:32 +00:00
return "", err
2022-11-26 23:26:14 +00:00
}
mimeType := http.DetectContentType(data)
mimeType = strings.Replace(mimeType, "text/xml", "image/svg+xml", 1)
2022-11-29 19:01:51 +00:00
enc := base64.StdEncoding.EncodeToString(data)
2022-11-26 23:26:14 +00:00
2022-11-29 18:20:32 +00:00
return fmt.Sprintf("data:%s;base64,%s", mimeType, enc), nil
2022-11-26 23:26:14 +00:00
}
2022-11-27 02:14:41 +00:00
2022-11-29 18:20:32 +00:00
func read(ctx context.Context, href string) (string, error) {
2022-11-27 02:14:41 +00:00
data, err := os.ReadFile(href)
if err != nil {
2022-11-29 18:20:32 +00:00
return "", err
2022-11-27 02:14:41 +00:00
}
mimeType := http.DetectContentType(data)
mimeType = strings.Replace(mimeType, "text/xml", "image/svg+xml", 1)
2022-11-29 19:01:51 +00:00
enc := base64.StdEncoding.EncodeToString(data)
2022-11-27 02:14:41 +00:00
2022-11-29 18:20:32 +00:00
return fmt.Sprintf("data:%s;base64,%s", mimeType, enc), nil
2022-11-27 02:14:41 +00:00
}