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
Move hosting from GitHub to private Gitea instance.
238 lines
6.4 KiB
Go
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
|
|
}
|