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:
		| @@ -1,6 +1,8 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## [Unreleased] | ||||
| ### Add | ||||
| - Add `IMGPROXY_SVG_FIX_UNSUPPORTED` config. | ||||
|  | ||||
| ## [3.8.0] - 2022-10-06 | ||||
| ### Add | ||||
|   | ||||
| @@ -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") | ||||
|   | ||||
| @@ -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`:  the content of the health check response. Default: `imgproxy is running` | ||||
| * `IMGPROXY_HEALTH_CHECK_PATH`: an additional path of the health check. Default: blank | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
							
								
								
									
										130
									
								
								svg/svg.go
									
									
									
									
									
								
							
							
						
						
									
										130
									
								
								svg/svg.go
									
									
									
									
									
								
							| @@ -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) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user