package renderer import ( "gitea.dockr.co/kev/go-jdenticon/internal/engine" ) // Renderer defines the interface for rendering identicons to various output formats. // It provides a set of drawing primitives that can be implemented by concrete renderers // such as SVG, PNG, or other custom formats. type Renderer interface { // Drawing primitives MoveTo(x, y float64) LineTo(x, y float64) CurveTo(x1, y1, x2, y2, x, y float64) ClosePath() // Fill and stroke operations Fill(color string) Stroke(color string, width float64) // Shape management BeginShape(color string) EndShape() // Background and configuration SetBackground(fillColor string, opacity float64) // High-level shape methods AddPolygon(points []engine.Point) AddCircle(topLeft engine.Point, size float64, invert bool) AddRectangle(x, y, width, height float64) AddTriangle(p1, p2, p3 engine.Point) // Utility methods GetSize() int Clear() } // BaseRenderer provides default implementations for common renderer functionality. // Concrete renderers can embed this struct and override specific methods as needed. type BaseRenderer struct { iconSize int currentColor string background string backgroundOp float64 // Current path state for primitive operations currentPath []PathCommand pathStart engine.Point currentPos engine.Point } // PathCommandType represents the type of path command type PathCommandType int const ( MoveToCommand PathCommandType = iota LineToCommand CurveToCommand ClosePathCommand ) // PathCommand represents a single drawing command in a path type PathCommand struct { Type PathCommandType Points []engine.Point } // NewBaseRenderer creates a new base renderer with the specified icon size func NewBaseRenderer(iconSize int) *BaseRenderer { return &BaseRenderer{ iconSize: iconSize, currentPath: make([]PathCommand, 0), } } // MoveTo moves the current drawing position to the specified coordinates func (r *BaseRenderer) MoveTo(x, y float64) { pos := engine.Point{X: x, Y: y} r.currentPos = pos r.pathStart = pos r.currentPath = append(r.currentPath, PathCommand{ Type: MoveToCommand, Points: []engine.Point{pos}, }) } // LineTo draws a line from the current position to the specified coordinates func (r *BaseRenderer) LineTo(x, y float64) { pos := engine.Point{X: x, Y: y} r.currentPos = pos r.currentPath = append(r.currentPath, PathCommand{ Type: LineToCommand, Points: []engine.Point{pos}, }) } // CurveTo draws a cubic Bézier curve from the current position to (x, y) using (x1, y1) and (x2, y2) as control points func (r *BaseRenderer) CurveTo(x1, y1, x2, y2, x, y float64) { endPos := engine.Point{X: x, Y: y} r.currentPos = endPos r.currentPath = append(r.currentPath, PathCommand{ Type: CurveToCommand, Points: []engine.Point{ {X: x1, Y: y1}, {X: x2, Y: y2}, endPos, }, }) } // ClosePath closes the current path by drawing a line back to the path start func (r *BaseRenderer) ClosePath() { r.currentPos = r.pathStart r.currentPath = append(r.currentPath, PathCommand{ Type: ClosePathCommand, Points: []engine.Point{}, }) } // Fill fills the current path with the specified color func (r *BaseRenderer) Fill(color string) { // Default implementation - concrete renderers should override } // Stroke strokes the current path with the specified color and width func (r *BaseRenderer) Stroke(color string, width float64) { // Default implementation - concrete renderers should override } // BeginShape starts a new shape with the specified color func (r *BaseRenderer) BeginShape(color string) { r.currentColor = color r.currentPath = make([]PathCommand, 0) } // EndShape ends the current shape func (r *BaseRenderer) EndShape() { // Default implementation - concrete renderers should override } // SetBackground sets the background color and opacity func (r *BaseRenderer) SetBackground(fillColor string, opacity float64) { r.background = fillColor r.backgroundOp = opacity } // AddPolygon adds a polygon to the renderer using the current fill color func (r *BaseRenderer) AddPolygon(points []engine.Point) { if len(points) == 0 { return } // Move to first point r.MoveTo(points[0].X, points[0].Y) // Line to subsequent points for i := 1; i < len(points); i++ { r.LineTo(points[i].X, points[i].Y) } // Close the path r.ClosePath() // Fill with current color r.Fill(r.currentColor) } // AddCircle adds a circle to the renderer using the current fill color func (r *BaseRenderer) AddCircle(topLeft engine.Point, size float64, invert bool) { // Approximate circle using cubic Bézier curves // Magic number for circle approximation with Bézier curves const kappa = 0.5522847498307936 // 4/3 * (sqrt(2) - 1) radius := size / 2 centerX := topLeft.X + radius centerY := topLeft.Y + radius cp := kappa * radius // Control point distance // Start at rightmost point r.MoveTo(centerX+radius, centerY) // Four cubic curves to approximate circle r.CurveTo(centerX+radius, centerY+cp, centerX+cp, centerY+radius, centerX, centerY+radius) r.CurveTo(centerX-cp, centerY+radius, centerX-radius, centerY+cp, centerX-radius, centerY) r.CurveTo(centerX-radius, centerY-cp, centerX-cp, centerY-radius, centerX, centerY-radius) r.CurveTo(centerX+cp, centerY-radius, centerX+radius, centerY-cp, centerX+radius, centerY) r.ClosePath() r.Fill(r.currentColor) } // AddRectangle adds a rectangle to the renderer func (r *BaseRenderer) AddRectangle(x, y, width, height float64) { points := []engine.Point{ {X: x, Y: y}, {X: x + width, Y: y}, {X: x + width, Y: y + height}, {X: x, Y: y + height}, } r.AddPolygon(points) } // AddTriangle adds a triangle to the renderer func (r *BaseRenderer) AddTriangle(p1, p2, p3 engine.Point) { points := []engine.Point{p1, p2, p3} r.AddPolygon(points) } // GetSize returns the icon size func (r *BaseRenderer) GetSize() int { return r.iconSize } // Clear clears the renderer state func (r *BaseRenderer) Clear() { r.currentPath = make([]PathCommand, 0) r.currentColor = "" r.background = "" r.backgroundOp = 0 } // GetCurrentPath returns the current path commands func (r *BaseRenderer) GetCurrentPath() []PathCommand { return r.currentPath } // GetCurrentColor returns the current drawing color func (r *BaseRenderer) GetCurrentColor() string { return r.currentColor } // GetBackground returns the background color and opacity func (r *BaseRenderer) GetBackground() (string, float64) { return r.background, r.backgroundOp }