1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-10-30 23:08:02 +02:00

Fix feDropShadow SVG filter when IMGPROXY_SVG_FIX_UNSUPPORTED is true

This commit is contained in:
DarthSim
2022-10-15 20:22:54 +06:00
parent f5f4fb6487
commit 4ab415fd9b
5 changed files with 151 additions and 0 deletions

View File

@@ -1,6 +1,8 @@
# Changelog
## [Unreleased]
### Add
- Add `IMGPROXY_SVG_FIX_UNSUPPORTED` config.
## [3.8.0] - 2022-10-06
### Add

View File

@@ -53,6 +53,7 @@ var (
AutoRotate bool
EnforceThumbnail bool
ReturnAttachment bool
SvgFixUnsupported bool
EnableWebpDetection bool
EnforceWebp bool
@@ -228,6 +229,7 @@ func Reset() {
AutoRotate = true
EnforceThumbnail = false
ReturnAttachment = false
SvgFixUnsupported = false
EnableWebpDetection = false
EnforceWebp = false
@@ -402,6 +404,7 @@ func Configure() error {
configurators.Bool(&AutoRotate, "IMGPROXY_AUTO_ROTATE")
configurators.Bool(&EnforceThumbnail, "IMGPROXY_ENFORCE_THUMBNAIL")
configurators.Bool(&ReturnAttachment, "IMGPROXY_RETURN_ATTACHMENT")
configurators.Bool(&SvgFixUnsupported, "IMGPROXY_SVG_FIX_UNSUPPORTED")
configurators.Bool(&EnableWebpDetection, "IMGPROXY_ENABLE_WEBP_DETECTION")
configurators.Bool(&EnforceWebp, "IMGPROXY_ENFORCE_WEBP")

View File

@@ -465,5 +465,6 @@ imgproxy can send logs to syslog, but this feature is disabled by default. To en
* `IMGPROXY_AUTO_ROTATE`: when `true`, imgproxy will automatically rotate images based on the EXIF Orientation parameter (if available in the image meta data). The orientation tag will be removed from the image in all cases. Default: `true`
* `IMGPROXY_ENFORCE_THUMBNAIL`: when `true` and the source image has an embedded thumbnail, imgproxy will always use the embedded thumbnail instead of the main image. Currently, only thumbnails embedded in `heic` and `avif` are supported. Default: `false`
* `IMGPROXY_RETURN_ATTACHMENT`: when `true`, response header `Content-Disposition` will include `attachment`. Default: `false`
* `IMGPROXY_SVG_FIX_UNSUPPORTED`: when `true`, imgproxy will try to replace SVG features unsupported by librsvg to minimize SVG rendering error. This config only takes effect on SVG rasterization. Default: `false`
* `IMGPROXY_HEALTH_CHECK_MESSAGE`: ![pro](/assets/pro.svg) the content of the health check response. Default: `imgproxy is running`
* `IMGPROXY_HEALTH_CHECK_PATH`: an additional path of the health check. Default: blank

View File

@@ -383,6 +383,21 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
))
}
// We're going to rasterize SVG. Since librsvg lacks the support of some SVG
// features, we're going to replace them to minimize rendering error
if originData.Type == imagetype.SVG && config.SvgFixUnsupported {
fixed, changed, svgErr := svg.FixUnsupported(originData)
checkErr(ctx, "svg_processing", svgErr)
if changed {
// Since we'll replace origin data, it's better to close it to return
// it's buffer to the pool
originData.Close()
originData = fixed
}
}
resultData, err := func() (*imagedata.ImageData, error) {
defer metrics.StartProcessingSegment(ctx)()
return processing.ProcessImage(ctx, originData, po)

View File

@@ -2,15 +2,31 @@ package svg
import (
"bytes"
"fmt"
"io"
"strings"
nanoid "github.com/matoous/go-nanoid/v2"
"github.com/tdewolff/parse/v2"
"github.com/tdewolff/parse/v2/xml"
"github.com/imgproxy/imgproxy/v3/imagedata"
)
var feDropShadowName = []byte("feDropShadow")
const feDropShadowTemplate = `
<feMerge result="dsin-%[1]s"><feMergeNode %[3]s /></feMerge>
<feGaussianBlur %[4]s />
<feOffset %[5]s result="dsof-%[2]s" />
<feFlood %[6]s />
<feComposite in2="dsof-%[2]s" operator="in" />
<feMerge %[7]s>
<feMergeNode />
<feMergeNode in="dsin-%[1]s" />
</feMerge>
`
func Satitize(data *imagedata.ImageData) (*imagedata.ImageData, error) {
r := bytes.NewReader(data.Data)
l := xml.NewLexer(parse.NewInput(r))
@@ -63,3 +79,117 @@ func Satitize(data *imagedata.ImageData) (*imagedata.ImageData, error) {
}
}
}
func replaceDropShadowNode(l *xml.Lexer, buf *bytes.Buffer) error {
inAttrs := new(bytes.Buffer)
blurAttrs := new(bytes.Buffer)
offsetAttrs := new(bytes.Buffer)
floodAttrs := new(bytes.Buffer)
finalAttrs := new(bytes.Buffer)
inID, _ := nanoid.New(8)
offsetID, _ := nanoid.New(8)
hasStdDeviation := false
hasDx := false
hasDy := false
TOKEN_LOOP:
for {
tt, tdata := l.Next()
switch tt {
case xml.ErrorToken:
if l.Err() != io.EOF {
return l.Err()
}
break TOKEN_LOOP
case xml.EndTagToken, xml.StartTagCloseVoidToken:
break TOKEN_LOOP
case xml.AttributeToken:
switch strings.ToLower(string(l.Text())) {
case "in":
inAttrs.Write(tdata)
case "stddeviation":
blurAttrs.Write(tdata)
hasStdDeviation = true
case "dx":
offsetAttrs.Write(tdata)
hasDx = true
case "dy":
offsetAttrs.Write(tdata)
hasDy = true
case "flood-color", "flood-opacity":
floodAttrs.Write(tdata)
default:
finalAttrs.Write(tdata)
}
}
}
if !hasStdDeviation {
blurAttrs.WriteString(` stdDeviation="2"`)
}
if !hasDx {
offsetAttrs.WriteString(` dx="2"`)
}
if !hasDy {
offsetAttrs.WriteString(` dy="2"`)
}
fmt.Fprintf(
buf, feDropShadowTemplate,
inID, offsetID,
inAttrs.String(),
blurAttrs.String(),
offsetAttrs.String(),
floodAttrs.String(),
finalAttrs.String(),
)
return nil
}
func FixUnsupported(data *imagedata.ImageData) (*imagedata.ImageData, bool, error) {
if !bytes.Contains(data.Data, feDropShadowName) {
return data, false, nil
}
r := bytes.NewReader(data.Data)
l := xml.NewLexer(parse.NewInput(r))
buf, cancel := imagedata.BorrowBuffer()
for {
tt, tdata := l.Next()
switch tt {
case xml.ErrorToken:
if l.Err() != io.EOF {
cancel()
return nil, false, l.Err()
}
newData := imagedata.ImageData{
Data: buf.Bytes(),
Type: data.Type,
}
newData.SetCancel(cancel)
return &newData, true, nil
case xml.StartTagToken:
if bytes.Equal(l.Text(), feDropShadowName) {
if err := replaceDropShadowNode(l, buf); err != nil {
cancel()
return nil, false, err
}
continue
}
buf.Write(tdata)
default:
buf.Write(tdata)
}
}
}