// Package scenepaint translates authored D2 paints into renderer-independent scenes.
package scenepaint import ( libcolor ) // Gradient translates the object-bounding-box gradient geometry emitted // by lib/color so the rasterizer does not need to parse CSS strings. func ( string) (d2scene.Paint, error) { , := libcolor.ParseGradient() if != nil { return nil, } if len(.ColorStops) == 0 { return nil, fmt.Errorf("gradient has no valid color stops") } := make([]d2scene.GradientStop, len(.ColorStops)) := 0.0 for , := range .ColorStops { , := csscolorparser.Parse(.Color) if != nil { return nil, fmt.Errorf("stop %d color %q: %w", , .Color, ) } , := gradientStopOffset(.Position, , len(.ColorStops)) if != nil { return nil, fmt.Errorf("stop %d position %q: %w", , .Position, ) } // SVG clamps stop offsets into [0,1] and prevents later stops from // moving behind an earlier one. = math.Max(, math.Max(0, math.Min(1, ))) = , , , := .RGBA255() [] = d2scene.GradientStop{Offset: , Color: color.NRGBA{R: , G: , B: , A: }} } switch .Type { case "linear": , , , , := linearGradientVector(.Direction) if != nil { return nil, } return d2scene.LinearGradient{ Start: d2scene.Point{X: , Y: }, End: d2scene.Point{X: , Y: }, Stops: , Units: d2scene.ObjectBoundingBox, Transform: d2scene.Identity(), Spread: d2scene.SpreadPad, }, nil case "radial": return d2scene.RadialGradient{ Center: d2scene.Point{X: .5, Y: .5}, Radius: .5, Focal: d2scene.Point{X: .5, Y: .5}, Stops: , Units: d2scene.ObjectBoundingBox, Transform: d2scene.Identity(), Spread: d2scene.SpreadPad, }, nil default: return nil, fmt.Errorf("unsupported gradient type %q", .Type) } } func gradientStopOffset( string, , int) (float64, error) { if == "" { if == 1 { return 0, nil } return float64() / float64(-1), nil } = strings.TrimSpace() := 1.0 if strings.HasSuffix(, "%") { = strings.TrimSpace(strings.TrimSuffix(, "%")) = 100 } , := strconv.ParseFloat(, 64) if != nil || math.IsNaN() || math.IsInf(, 0) { return 0, fmt.Errorf("must be a finite number or percentage") } return / , nil } func linearGradientVector( string) (, , , float64, error) { , , , = 0, 0, 0, 1 = strings.TrimSpace() if strings.HasPrefix(, "to ") { , , , = .5, .5, .5, .5 , := false, false for , := range strings.Fields(strings.TrimSpace(strings.TrimPrefix(, "to "))) { switch { case "left": , , = 1, 0, true case "right": , , = 0, 1, true case "top": , , = 1, 0, true case "bottom": , , = 0, 1, true default: return 0, 0, 0, 0, fmt.Errorf("unsupported linear-gradient direction %q", ) } } if ! { , = .5, .5 } if ! { , = .5, .5 } return , , , , nil } if strings.HasSuffix(, "deg") { , := strconv.ParseFloat(strings.TrimSpace(strings.TrimSuffix(, "deg")), 64) if != nil || math.IsNaN() || math.IsInf(, 0) { return 0, 0, 0, 0, fmt.Errorf("invalid linear-gradient angle %q", ) } := (90 - ) * math.Pi / 180 return .5, .5, .5 + .5*math.Cos(), .5 + .5*math.Sin(), nil } if != "" { return 0, 0, 0, 0, fmt.Errorf("unsupported linear-gradient direction %q", ) } return , , , , nil }