@@ -15,7 +15,9 @@ import (
1515 "bytes"
1616 "encoding/xml"
1717 "image"
18+ "image/color"
1819 "io"
20+ "math"
1921 "os"
2022 "path"
2123 "path/filepath"
@@ -236,9 +238,19 @@ func (f *File) AddPictureFromBytes(sheet, cell string, pic *Picture) error {
236238 return ErrParameterInvalid
237239 }
238240 options := parseGraphicOptions (pic .Format )
239- img , _ , err := image .DecodeConfig (bytes .NewReader (pic .File ))
240- if err != nil {
241- return err
241+ var img image.Config
242+ if ext == ".svg" {
243+ cfg , err := svgDecodeConfig (pic .File )
244+ if err != nil {
245+ return err
246+ }
247+ img = cfg
248+ } else {
249+ cfg , _ , err := image .DecodeConfig (bytes .NewReader (pic .File ))
250+ if err != nil {
251+ return err
252+ }
253+ img = cfg
242254 }
243255 // Read sheet data
244256 f .mu .Lock ()
@@ -286,6 +298,109 @@ func (f *File) AddPictureFromBytes(sheet, cell string, pic *Picture) error {
286298 return err
287299}
288300
301+ // --- SVG helpers ---
302+
303+ type svgRoot struct {
304+ Width string `xml:"width,attr"`
305+ Height string `xml:"height,attr"`
306+ ViewBox string `xml:"viewBox,attr"`
307+ }
308+
309+ // svgUnitToPx converts a numeric string with an optional unit suffix
310+ // (e.g. "16px", "12pt", "1in") to pixels using a 96-DPI scale,
311+ // which is the standard DPI for Office/Excel.
312+ func svgUnitToPx (s string ) (float64 , bool ) {
313+ s = strings .TrimSpace (s )
314+ if s == "" {
315+ return 0 , false
316+ }
317+ i := len (s )
318+ for i > 0 && (s [i - 1 ] < '0' || s [i - 1 ] > '9' ) && s [i - 1 ] != '.' {
319+ i --
320+ }
321+ num := strings .TrimSpace (s [:i ])
322+ unit := strings .ToLower (strings .TrimSpace (s [i :]))
323+
324+ v , err := strconv .ParseFloat (num , 64 )
325+ if err != nil {
326+ return 0 , false
327+ }
328+ switch unit {
329+ case "" , "px" :
330+ return v , true
331+ case "pt" :
332+ return v * (96.0 / 72.0 ), true
333+ case "in" :
334+ return v * 96.0 , true
335+ case "mm" :
336+ return v * (96.0 / 25.4 ), true
337+ case "cm" :
338+ return v * (96.0 / 2.54 ), true
339+ default :
340+ return 0 , false
341+ }
342+ }
343+
344+ // svgDecodeConfig extracts approximate image dimensions for SVG files
345+ // based on the <svg> element's width/height attributes or its viewBox.
346+ // Only the root <svg ...> element is parsed; the SVG content is not rendered.
347+ func svgDecodeConfig (b []byte ) (image.Config , error ) {
348+ var root svgRoot
349+ dec := xml .NewDecoder (bytes .NewReader (b ))
350+ dec .Strict = false
351+ dec .AutoClose = xml .HTMLAutoClose
352+ dec .Entity = xml .HTMLEntity
353+
354+ // Read only the root <svg> element and decode its attributes.
355+ for {
356+ tok , err := dec .Token ()
357+ if err != nil {
358+ return image.Config {}, err
359+ }
360+ if se , ok := tok .(xml.StartElement ); ok && strings .EqualFold (se .Name .Local , "svg" ) {
361+ if err := dec .DecodeElement (& root , & se ); err != nil && err != io .EOF {
362+ return image.Config {}, err
363+ }
364+ break
365+ }
366+ }
367+
368+ // 1) Use width/height attributes if both are present and valid.
369+ if wpx , okW := svgUnitToPx (root .Width ); okW {
370+ if hpx , okH := svgUnitToPx (root .Height ); okH {
371+ return image.Config {
372+ ColorModel : color .RGBAModel ,
373+ Width : int (math .Max (1 , math .Round (wpx ))),
374+ Height : int (math .Max (1 , math .Round (hpx ))),
375+ }, nil
376+ }
377+ }
378+
379+ // 2) Otherwise, try to infer dimensions from the viewBox attribute:
380+ // "minX minY width height"
381+ if root .ViewBox != "" {
382+ parts := strings .Fields (root .ViewBox )
383+ if len (parts ) == 4 {
384+ if vw , err1 := strconv .ParseFloat (parts [2 ], 64 ); err1 == nil && vw > 0 {
385+ if vh , err2 := strconv .ParseFloat (parts [3 ], 64 ); err2 == nil && vh > 0 {
386+ return image.Config {
387+ ColorModel : color .RGBAModel ,
388+ Width : int (math .Max (1 , math .Round (vw ))),
389+ Height : int (math .Max (1 , math .Round (vh ))),
390+ }, nil
391+ }
392+ }
393+ }
394+ }
395+
396+ // 3) Fallback to a default icon-sized bounding box if nothing is specified.
397+ return image.Config {
398+ ColorModel : color .RGBAModel ,
399+ Width : 16 ,
400+ Height : 16 ,
401+ }, nil
402+ }
403+
289404// addSheetLegacyDrawing provides a function to add legacy drawing element to
290405// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
291406func (f * File ) addSheetLegacyDrawing (sheet string , rID int ) {
0 commit comments