ref: 96375d3675de6ca7e7cc4d3a841d0c6a61cd7d73
dir: /img/img.go/
package img
import (
"bytes"
"github.com/nfnt/resize"
"encoding/base64"
"fmt"
"github.com/srwiley/oksvg"
"github.com/srwiley/rasterx"
"image"
"image/png"
"io"
"github.com/psilva261/opossum"
"github.com/psilva261/opossum/logger"
"strings"
"net/url"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
)
const SrcZero = "//:0"
var log *logger.Logger
func SetLogger(l *logger.Logger) {
log = l
}
func parseDataUri(addr string) (data []byte, ct opossum.ContentType, err error) {
addr = strings.TrimPrefix(addr, "data:")
if strings.Contains(addr, "charset=UTF-8") {
return nil, ct, fmt.Errorf("cannot handle charset")
}
parts := strings.Split(addr, ",")
var ctStr string
if strings.Contains(parts[0], ";") {
header := strings.Split(parts[0], ";")
ctStr = header[0]
} else {
ctStr = parts[0]
}
if ct, err = opossum.NewContentType(ctStr, nil); err != nil {
return nil, ct, fmt.Errorf("content type: %v: %w", ctStr, err)
}
if len(parts) == 1 {
return nil, ct, fmt.Errorf("empty: %v", addr)
}
if strings.Contains(addr, "base64") {
e := base64.RawStdEncoding
if strings.HasSuffix(addr, "=") {
e = base64.StdEncoding
}
if data, err = e.DecodeString(parts[1]); err != nil {
return nil, ct, fmt.Errorf("base64 decode %v src: %w", addr, err)
}
} else {
out, err := url.QueryUnescape(parts[1])
if err != nil {
return nil, ct, fmt.Errorf("url decode: %w", err)
}
data = []byte(out)
}
return
}
func quoteAttrsInTag(s string) string {
eqs := make([]int, 0, 5)
offset := 0
for {
i := strings.Index(s[offset:], "=")
if i >= 0 {
eqs = append(eqs, i+offset)
offset += i + 1
} else {
break
}
}
keyStarts := make([]int, len(eqs))
for i, eq := range eqs {
j := strings.LastIndex(s[:eq], " ")
keyStarts[i] = j
}
valueEnds := make([]int, len(keyStarts))
for i, _ := range keyStarts {
if i+1 < len(keyStarts) {
valueEnds[i] = keyStarts[i+1]
} else {
off := eqs[i]
jj := strings.Index(s[off:], ">")
valueEnds[i] = jj+off
if s[valueEnds[i]-1:valueEnds[i]] == "/" {
valueEnds[i]--
}
}
}
for i := len(eqs) - 1; i >= 0; i-- {
s = s[:valueEnds[i]] + `"` + s[valueEnds[i]:]
s = s[:eqs[i]+1] + `"` + s[eqs[i]+1:]
}
return s
}
func quoteAttrs(s string) string {
if strings.Contains(s, `"`) {
return s
}
tagStarts := make([]int, 0, 5)
tagEnds := make([]int, 0, 5)
offset := 0
for {
i := strings.Index(s[offset:], "<")
if i >= 0 {
tagStarts = append(tagStarts, i+offset)
offset += i + 1
} else {
break
}
}
offset = 0
for {
i := strings.Index(s[offset:], ">")
if i >= 0 {
tagEnds = append(tagEnds, i+offset)
offset += i + 1
} else {
break
}
}
if len(tagStarts) != len(tagEnds) {
log.Errorf("quoteAttrs: len(tagStarts) != len(tagEnds)")
return s
}
for i := len(tagStarts) - 1; i >= 0; i-- {
from := tagStarts[i]
to := tagEnds[i] + 1
q := quoteAttrsInTag(s[from:to])
s = s[:tagStarts[i]] + q + s[tagEnds[i]+1:]
}
return s
}
// Svg returns the svg+xml encoded as jpg with the sizing defined in
// viewbox unless w and h != 0
func Svg(data string, w, h int) (bs []byte, err error) {
data = strings.ReplaceAll(data, "currentColor", "black")
data = strings.ReplaceAll(data, "inherit", "black")
data = quoteAttrs(data)
r := bytes.NewReader([]byte(data))
icon, err := oksvg.ReadIconStream(r)
if err != nil {
return nil, fmt.Errorf("read icon stream: %w", err)
}
if w == 0 || h == 0 {
w = int(icon.ViewBox.W)
h = int(icon.ViewBox.H)
}
icon.SetTarget(0, 0, float64(w), float64(h))
rgba := image.NewRGBA(image.Rect(0, 0, w, h))
icon.Draw(rasterx.NewDasher(w, h, rasterx.NewScannerGV(w, h, rgba, rgba.Bounds())), 1)
buf := bytes.NewBufferString("")
if err = png.Encode(buf, rgba); err != nil {
return nil, fmt.Errorf("encode: %w", err)
}
return buf.Bytes(), nil
}
// Load and resize to w and h if != 0
func Load(f opossum.Fetcher, src string, w, h int) (r io.Reader, err error) {
var imgUrl *url.URL
var data []byte
var contentType opossum.ContentType
if strings.HasPrefix(src, "data:") {
if data, contentType, err = parseDataUri(src); err != nil {
return nil, fmt.Errorf("parse data uri %v: %w", src, err)
}
} else {
if imgUrl, err = f.LinkedUrl(src); err != nil {
return nil, err
}
if data, contentType, err = f.Get(imgUrl); err != nil {
return nil, fmt.Errorf("get %v: %w", imgUrl, err)
}
}
if contentType.IsSvg() {
data, err = Svg(string(data), w, h)
if err != nil {
return nil, fmt.Errorf("svg: %v", err)
}
} else if w != 0 || h != 0 {
image, _, err := image.Decode(bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("decode %v: %w", imgUrl, err)
}
newImage := resize.Resize(uint(w), uint(h), image, resize.Lanczos3)
// Encode uses a Writer, use a Buffer if you need the raw []byte
buf := bytes.NewBufferString("")
if err = png.Encode(buf, newImage); err != nil {
return nil, fmt.Errorf("encode: %w", err)
}
data = buf.Bytes()
}
return bytes.NewReader(data), nil
}