Files
go-jdenticon/internal/renderer/renderer.go
Kevin McIntyre f1544ef49c
Some checks failed
CI / Test (Go 1.24.x, ubuntu-latest) (push) Successful in 1m53s
CI / Code Quality (push) Failing after 26s
CI / Security Scan (push) Failing after 11s
CI / Test Coverage (push) Successful in 1m13s
CI / Benchmarks (push) Failing after 10m22s
CI / Build CLI (push) Failing after 8s
Benchmarks / Run Benchmarks (push) Failing after 10m13s
Release / Test (push) Successful in 55s
Release / Build (amd64, darwin, ) (push) Failing after 12s
Release / Build (amd64, linux, ) (push) Failing after 6s
Release / Build (amd64, windows, .exe) (push) Failing after 12s
Release / Build (arm64, darwin, ) (push) Failing after 12s
Release / Build (arm64, linux, ) (push) Failing after 12s
Release / Release (push) Has been skipped
CI / Test (Go 1.24.x, macos-latest) (push) Has been cancelled
CI / Test (Go 1.24.x, windows-latest) (push) Has been cancelled
chore: update module path to gitea.dockr.co/kev/go-jdenticon
Move hosting from GitHub to private Gitea instance.
2026-02-10 10:07:57 -05:00

238 lines
6.4 KiB
Go

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
}